@cortexkit/opencode-magic-context 0.30.7 → 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +1 -0
  2. package/dist/agents/magic-context-prompt.d.ts +1 -1
  3. package/dist/agents/magic-context-prompt.d.ts.map +1 -1
  4. package/dist/config/schema/magic-context.d.ts +5 -10
  5. package/dist/config/schema/magic-context.d.ts.map +1 -1
  6. package/dist/features/builtin-commands/commands.d.ts.map +1 -1
  7. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  8. package/dist/features/magic-context/memory/project-identity.d.ts +18 -5
  9. package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
  10. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  11. package/dist/features/magic-context/search.d.ts +13 -2
  12. package/dist/features/magic-context/search.d.ts.map +1 -1
  13. package/dist/features/magic-context/smart-notes/storage.d.ts.map +1 -1
  14. package/dist/features/magic-context/storage-db.d.ts +1 -1
  15. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  16. package/dist/features/magic-context/storage-meta-persisted.d.ts +26 -1
  17. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  18. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  19. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  20. package/dist/features/magic-context/storage-schema-helpers.d.ts +1 -1
  21. package/dist/features/magic-context/storage-schema-helpers.d.ts.map +1 -1
  22. package/dist/features/magic-context/storage-v22-backfill-failures.d.ts +3 -1
  23. package/dist/features/magic-context/storage-v22-backfill-failures.d.ts.map +1 -1
  24. package/dist/features/magic-context/storage.d.ts +1 -1
  25. package/dist/features/magic-context/storage.d.ts.map +1 -1
  26. package/dist/features/magic-context/transform-decision-log.d.ts +1 -1
  27. package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
  28. package/dist/features/magic-context/v22-deferred-backfill.d.ts.map +1 -1
  29. package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -1
  30. package/dist/hooks/magic-context/caveman-cleanup.d.ts +1 -1
  31. package/dist/hooks/magic-context/command-handler.d.ts +11 -0
  32. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  33. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  34. package/dist/hooks/magic-context/compartment-runner-types.d.ts +10 -2
  35. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  36. package/dist/hooks/magic-context/compartment-runner.d.ts +2 -1
  37. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts +11 -1
  39. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/heuristic-cleanup.d.ts +2 -3
  41. package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/hook-handlers.d.ts +0 -2
  43. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/hook.d.ts +0 -1
  45. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  46. package/dist/hooks/magic-context/inject-compartments.d.ts +2 -0
  47. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/protected-tail-boundary.d.ts +16 -1
  49. package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +1 -1
  51. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  52. package/dist/hooks/magic-context/system-prompt-hash.d.ts +2 -3
  53. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  54. package/dist/hooks/magic-context/tag-messages.d.ts +3 -4
  55. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  56. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +3 -3
  57. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  58. package/dist/hooks/magic-context/transform.d.ts +8 -12
  59. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  60. package/dist/hooks/magic-context/wrapup-orchestrator.d.ts +13 -0
  61. package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -0
  62. package/dist/index.js +1324 -171
  63. package/dist/plugin/hooks/create-session-hooks.d.ts +0 -1
  64. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  65. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  66. package/dist/plugin/tool-registry.d.ts.map +1 -1
  67. package/dist/shared/announcement.d.ts +1 -1
  68. package/dist/shared/announcement.d.ts.map +1 -1
  69. package/dist/shared/rpc-types.d.ts +1 -2
  70. package/dist/shared/rpc-types.d.ts.map +1 -1
  71. package/dist/shared/tag-transcript.d.ts +3 -4
  72. package/dist/shared/tag-transcript.d.ts.map +1 -1
  73. package/dist/tools/ctx-search/constants.d.ts +1 -1
  74. package/dist/tools/ctx-search/constants.d.ts.map +1 -1
  75. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  76. package/dist/tools/ctx-search/types.d.ts +1 -1
  77. package/dist/tools/ctx-search/types.d.ts.map +1 -1
  78. package/dist/tui/data/context-db.d.ts.map +1 -1
  79. package/package.json +19 -5
  80. package/src/shared/announcement.ts +5 -4
  81. package/src/shared/rpc-types.ts +1 -2
  82. package/src/shared/tag-transcript.ts +3 -4
  83. package/src/tui/data/context-db.ts +0 -1
  84. package/src/tui/index.tsx +9 -1
  85. package/src/tui/slots/sidebar-content.tsx +6 -7
package/dist/index.js CHANGED
@@ -15372,7 +15372,6 @@ var init_magic_context = __esm(() => {
15372
15372
  enabled: exports_external.boolean().default(true).describe("Enable magic context (default: true)"),
15373
15373
  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."),
15374
15374
  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."),
15375
- 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)"),
15376
15375
  historian: HistorianConfigSchema.describe("Historian agent configuration (model, fallback_models, variant, temperature, maxTokens, permission, two_pass, etc.)"),
15377
15376
  dreamer: DreamerConfigSchema.optional().describe("Dreamer agent + scheduling configuration (model, fallback_models, disable, schedule, tasks, etc.)"),
15378
15377
  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" })'),
@@ -15411,9 +15410,9 @@ var init_magic_context = __esm(() => {
15411
15410
  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."),
15412
15411
  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."),
15413
15412
  caveman_text_compression: exports_external.object({
15414
- 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%)."),
15413
+ 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%)."),
15415
15414
  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.")
15416
- }).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)."),
15415
+ }).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)."),
15417
15416
  memory: exports_external.object({
15418
15417
  enabled: exports_external.boolean().default(true).describe("Enable cross-session memory (default: true)"),
15419
15418
  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)"),
@@ -15783,7 +15782,7 @@ var init_compaction_marker = __esm(async () => {
15783
15782
  // src/features/magic-context/memory/project-identity.ts
15784
15783
  import { execFileSync } from "node:child_process";
15785
15784
  import { createHash } from "node:crypto";
15786
- import { statSync as statSync2 } from "node:fs";
15785
+ import { existsSync as existsSync8, realpathSync, statSync as statSync2 } from "node:fs";
15787
15786
  import path3 from "node:path";
15788
15787
  function asError(error51) {
15789
15788
  return error51 instanceof Error ? error51 : undefined;
@@ -15860,6 +15859,9 @@ function classifyGitError(error51, rawDirectory) {
15860
15859
  return new ProjectIdentityError("permission_denied", rawDirectory, "Permission denied while spawning git", asError(error51));
15861
15860
  }
15862
15861
  const stderr = getErrorStderr(error51).toLowerCase();
15862
+ if (stderr.includes("detected dubious ownership")) {
15863
+ return new ProjectIdentityError("dubious_ownership", rawDirectory, "git refused to read the repository because it detected dubious ownership", asError(error51));
15864
+ }
15863
15865
  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")) {
15864
15866
  return new ProjectIdentityError("not_git_repo", rawDirectory, "Directory has no git root commit; caller may use directory fallback", asError(error51));
15865
15867
  }
@@ -15872,9 +15874,12 @@ function resolveProjectIdentityStrict(directory) {
15872
15874
  return cached2;
15873
15875
  }
15874
15876
  assertDirectoryUsable(canonical, directory);
15877
+ if (!hasGitDir(canonical)) {
15878
+ throw new ProjectIdentityError("not_git_repo", directory, "Directory has no git metadata; caller may use directory fallback");
15879
+ }
15875
15880
  let output;
15876
15881
  try {
15877
- output = execFileSync("git", ["rev-list", "--max-parents=0", "HEAD"], {
15882
+ output = execFileSyncForIdentity("git", ["rev-list", "--max-parents=0", "HEAD"], {
15878
15883
  cwd: canonical,
15879
15884
  encoding: "utf8",
15880
15885
  env: { ...process.env, LC_ALL: "C", LANG: "C" },
@@ -15892,10 +15897,82 @@ function resolveProjectIdentityStrict(directory) {
15892
15897
  }
15893
15898
  const identity = `git:${rootCommit}`;
15894
15899
  identityCache.set(canonical, identity);
15900
+ lastKnownGitIdentityCache.set(canonical, identity);
15901
+ transientFailureCooldown.delete(canonical);
15902
+ dubiousOwnershipFallbackDirectories.delete(canonical);
15903
+ transientGitIdentityReuseLoggedDirectories.delete(canonical);
15895
15904
  return identity;
15896
15905
  }
15897
15906
  function shouldUseDirectoryFallback(error51) {
15898
- return error51.errorClass === "not_git_repo" || error51.errorClass === "unknown" && error51.message.startsWith("Unable to access project directory:");
15907
+ return error51.errorClass !== "permission_denied";
15908
+ }
15909
+ function getActiveCooldown(canonical) {
15910
+ const until = transientFailureCooldown.get(canonical);
15911
+ if (until === undefined)
15912
+ return;
15913
+ if (nowMs() < until)
15914
+ return until;
15915
+ transientFailureCooldown.delete(canonical);
15916
+ return;
15917
+ }
15918
+ function lastKnownGitIdentity(canonical) {
15919
+ return lastKnownGitIdentityCache.get(canonical) ?? identityCache.get(canonical);
15920
+ }
15921
+ function nearestLastKnownGitIdentity(canonical) {
15922
+ const visited = new Set;
15923
+ const walk = (start) => {
15924
+ let current = start;
15925
+ while (!visited.has(current)) {
15926
+ visited.add(current);
15927
+ const cached2 = lastKnownGitIdentity(current);
15928
+ if (cached2 !== undefined)
15929
+ return { identity: cached2, source: current };
15930
+ const parent = path3.dirname(current);
15931
+ if (parent === current)
15932
+ break;
15933
+ current = parent;
15934
+ }
15935
+ return;
15936
+ };
15937
+ const exactOrAncestor = walk(canonical);
15938
+ if (exactOrAncestor)
15939
+ return exactOrAncestor;
15940
+ try {
15941
+ const realCanonical = realpathSync.native(canonical);
15942
+ if (realCanonical !== canonical)
15943
+ return walk(realCanonical);
15944
+ } catch {}
15945
+ return;
15946
+ }
15947
+ function reuseLastKnownGitIdentity(canonical) {
15948
+ const cached2 = nearestLastKnownGitIdentity(canonical);
15949
+ if (cached2 === undefined)
15950
+ return;
15951
+ if (!transientGitIdentityReuseLoggedDirectories.has(canonical)) {
15952
+ transientGitIdentityReuseLoggedDirectories.add(canonical);
15953
+ const sourceNote = cached2.source === canonical ? "" : ` from ancestor ${cached2.source}`;
15954
+ log(`[magic-context] git identity resolution is temporarily unavailable for ${canonical}; reusing the last successful project identity${sourceNote} to avoid splitting project-scoped memory`);
15955
+ }
15956
+ return cached2.identity;
15957
+ }
15958
+ function formatDubiousOwnershipWarning(canonical) {
15959
+ 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}`;
15960
+ }
15961
+ function recordDubiousOwnershipFallback(canonical) {
15962
+ dubiousOwnershipFallbackDirectories.add(canonical);
15963
+ if (dubiousOwnershipLoggedDirectories.has(canonical))
15964
+ return;
15965
+ dubiousOwnershipLoggedDirectories.add(canonical);
15966
+ log(`[magic-context] ${formatDubiousOwnershipWarning(canonical)}`);
15967
+ }
15968
+ function takeDubiousOwnershipProjectIdentityWarning(directory) {
15969
+ const canonical = path3.resolve(directory);
15970
+ if (!dubiousOwnershipFallbackDirectories.has(canonical))
15971
+ return null;
15972
+ if (dubiousOwnershipWarnedDirectories.has(canonical))
15973
+ return null;
15974
+ dubiousOwnershipWarnedDirectories.add(canonical);
15975
+ return formatDubiousOwnershipWarning(canonical);
15899
15976
  }
15900
15977
  function resolveProjectIdentity(directory) {
15901
15978
  const canonical = path3.resolve(directory);
@@ -15906,27 +15983,74 @@ function resolveProjectIdentity(directory) {
15906
15983
  }
15907
15984
  directoryFallbackCache.delete(canonical);
15908
15985
  }
15986
+ if (getActiveCooldown(canonical) !== undefined) {
15987
+ if (hasGitDir(canonical)) {
15988
+ const cachedGitIdentity = reuseLastKnownGitIdentity(canonical);
15989
+ if (cachedGitIdentity !== undefined) {
15990
+ return cachedGitIdentity;
15991
+ }
15992
+ }
15993
+ return directoryFallback(canonical);
15994
+ }
15909
15995
  try {
15910
15996
  return resolveProjectIdentityStrict(directory);
15911
15997
  } catch (error51) {
15912
15998
  if (error51 instanceof ProjectIdentityError && shouldUseDirectoryFallback(error51)) {
15913
15999
  const fallback = directoryFallback(canonical);
15914
- if (!hasGitDir(canonical)) {
16000
+ const hasGitMetadata = hasGitDir(canonical);
16001
+ if (!hasGitMetadata) {
15915
16002
  directoryFallbackCache.set(canonical, fallback);
16003
+ transientFailureCooldown.delete(canonical);
16004
+ } else {
16005
+ transientFailureCooldown.set(canonical, nowMs() + TRANSIENT_FAILURE_COOLDOWN_MS);
16006
+ const cachedGitIdentity = reuseLastKnownGitIdentity(canonical);
16007
+ if (cachedGitIdentity !== undefined) {
16008
+ return cachedGitIdentity;
16009
+ }
16010
+ }
16011
+ if (error51.errorClass === "dubious_ownership") {
16012
+ recordDubiousOwnershipFallback(canonical);
15916
16013
  }
15917
16014
  return fallback;
15918
16015
  }
15919
16016
  throw error51;
15920
16017
  }
15921
16018
  }
15922
- function hasGitDir(canonical) {
16019
+ function resolveProjectIdentityOrFallback(directory) {
15923
16020
  try {
15924
- statSync2(path3.join(canonical, ".git"));
16021
+ return resolveProjectIdentity(directory);
16022
+ } catch (error51) {
16023
+ const canonical = path3.resolve(directory);
16024
+ const fallback = directoryFallback(canonical);
16025
+ const message = error51 instanceof Error ? error51.message : String(error51);
16026
+ log(`[magic-context] project identity resolution failed for ${canonical}; using directory fallback ${fallback}: ${message}`);
16027
+ return fallback;
16028
+ }
16029
+ }
16030
+ function hasGitDir(canonical) {
16031
+ if (hasGitDirInAncestorChain(canonical)) {
15925
16032
  return true;
16033
+ }
16034
+ try {
16035
+ const realCanonical = realpathSync.native(canonical);
16036
+ return realCanonical !== canonical && hasGitDirInAncestorChain(realCanonical);
15926
16037
  } catch {
15927
16038
  return false;
15928
16039
  }
15929
16040
  }
16041
+ function hasGitDirInAncestorChain(startDirectory) {
16042
+ let current = startDirectory;
16043
+ while (true) {
16044
+ if (existsSync8(path3.join(current, ".git"))) {
16045
+ return true;
16046
+ }
16047
+ const parent = path3.dirname(current);
16048
+ if (parent === current) {
16049
+ return false;
16050
+ }
16051
+ current = parent;
16052
+ }
16053
+ }
15930
16054
  function normalizeStoredProjectPath(rawOrStored) {
15931
16055
  if (rawOrStored.startsWith("git:") || rawOrStored.startsWith("dir:")) {
15932
16056
  return rawOrStored;
@@ -15940,10 +16064,19 @@ function normalizeStoredProjectPath(rawOrStored) {
15940
16064
  function storedPathBelongsToIdentity(storedProjectPath, projectIdentity) {
15941
16065
  return storedProjectPath === projectIdentity || normalizeStoredProjectPath(storedProjectPath) === projectIdentity;
15942
16066
  }
15943
- var GIT_TIMEOUT_MS = 5000, identityCache, directoryFallbackCache, ProjectIdentityError;
16067
+ var GIT_TIMEOUT_MS = 5000, TRANSIENT_FAILURE_COOLDOWN_MS, identityCache, lastKnownGitIdentityCache, directoryFallbackCache, transientFailureCooldown, dubiousOwnershipFallbackDirectories, dubiousOwnershipLoggedDirectories, dubiousOwnershipWarnedDirectories, transientGitIdentityReuseLoggedDirectories, execFileSyncForIdentity, nowMs = () => Date.now(), ProjectIdentityError;
15944
16068
  var init_project_identity = __esm(() => {
16069
+ init_logger();
16070
+ TRANSIENT_FAILURE_COOLDOWN_MS = 5 * 60 * 1000;
15945
16071
  identityCache = new Map;
16072
+ lastKnownGitIdentityCache = new Map;
15946
16073
  directoryFallbackCache = new Map;
16074
+ transientFailureCooldown = new Map;
16075
+ dubiousOwnershipFallbackDirectories = new Set;
16076
+ dubiousOwnershipLoggedDirectories = new Set;
16077
+ dubiousOwnershipWarnedDirectories = new Set;
16078
+ transientGitIdentityReuseLoggedDirectories = new Set;
16079
+ execFileSyncForIdentity = execFileSync;
15947
16080
  ProjectIdentityError = class ProjectIdentityError extends Error {
15948
16081
  errorClass;
15949
16082
  rawDirectory;
@@ -17144,13 +17277,13 @@ function removeSystemReminders(text) {
17144
17277
  var SYSTEM_DIRECTIVE_PREFIX = "[SYSTEM DIRECTIVE: MAGIC-CONTEXT";
17145
17278
 
17146
17279
  // src/hooks/magic-context/read-session-db.ts
17147
- import { existsSync as existsSync9 } from "node:fs";
17280
+ import { existsSync as existsSync10 } from "node:fs";
17148
17281
  import { join as join4 } from "node:path";
17149
17282
  function getOpenCodeDbPath2() {
17150
17283
  return join4(getDataDir(), "opencode", "opencode.db");
17151
17284
  }
17152
17285
  function openCodeDbExists() {
17153
- return existsSync9(getOpenCodeDbPath2());
17286
+ return existsSync10(getOpenCodeDbPath2());
17154
17287
  }
17155
17288
  function closeCachedReadOnlyDb() {
17156
17289
  if (!cachedReadOnlyDb) {
@@ -152764,6 +152897,15 @@ var init_migrations = __esm(() => {
152764
152897
  );
152765
152898
  `);
152766
152899
  }
152900
+ },
152901
+ {
152902
+ version: 50,
152903
+ description: "add durable ctx-wrapup session marker",
152904
+ up(db) {
152905
+ if (tableExists2(db, "session_meta")) {
152906
+ ensureColumn(db, "session_meta", "wrapup_in_progress_state", "TEXT");
152907
+ }
152908
+ }
152767
152909
  }
152768
152910
  ];
152769
152911
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
@@ -153010,7 +153152,7 @@ var init_tool_definition_tokens = __esm(() => {
153010
153152
  });
153011
153153
 
153012
153154
  // src/features/magic-context/tool-owner-backfill.ts
153013
- import { existsSync as existsSync10 } from "node:fs";
153155
+ import { existsSync as existsSync11 } from "node:fs";
153014
153156
  import { join as join5 } from "node:path";
153015
153157
  function resolveOpencodeDbPath() {
153016
153158
  return join5(getDataDir(), "opencode", "opencode.db");
@@ -153048,7 +153190,7 @@ function runToolOwnerBackfill(db) {
153048
153190
  return result;
153049
153191
  }
153050
153192
  const opencodeDbPath = resolveOpencodeDbPath();
153051
- if (!existsSync10(opencodeDbPath)) {
153193
+ if (!existsSync11(opencodeDbPath)) {
153052
153194
  log(`[backfill] OpenCode DB not found at ${opencodeDbPath} — marking all unbackfilled sessions as skipped. Lazy adoption (defense-in-depth) handles legacy rows at runtime.`);
153053
153195
  markAllUnbackfilledSessionsSkipped(db);
153054
153196
  result.sessionsSkippedNoOcDb = countSessionsByStatus(db, "skipped");
@@ -153272,7 +153414,7 @@ var init_tool_owner_backfill = __esm(() => {
153272
153414
  });
153273
153415
 
153274
153416
  // src/features/magic-context/storage-db.ts
153275
- import { chmodSync, copyFileSync, cpSync, existsSync as existsSync11, mkdirSync as mkdirSync4, mkdtempSync } from "node:fs";
153417
+ import { chmodSync, copyFileSync, cpSync, existsSync as existsSync12, mkdirSync as mkdirSync4, mkdtempSync } from "node:fs";
153276
153418
  import { dirname as dirname4, join as join6 } from "node:path";
153277
153419
  function getSchemaFenceRejection() {
153278
153420
  return lastSchemaFenceRejection;
@@ -153292,7 +153434,7 @@ function restrictDatabaseFilePermissions(dbPath) {
153292
153434
  return;
153293
153435
  for (const suffix of ["", "-wal", "-shm"]) {
153294
153436
  const file2 = `${dbPath}${suffix}`;
153295
- if (!existsSync11(file2))
153437
+ if (!existsSync12(file2))
153296
153438
  continue;
153297
153439
  try {
153298
153440
  chmodSync(file2, 384);
@@ -153318,11 +153460,11 @@ function getDatabasePath(db) {
153318
153460
  return pathByDatabase.get(db) ?? null;
153319
153461
  }
153320
153462
  function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
153321
- if (existsSync11(targetDbPath))
153463
+ if (existsSync12(targetDbPath))
153322
153464
  return;
153323
153465
  const legacyDir = getLegacyOpenCodeMagicContextStorageDir();
153324
153466
  const legacyDbPath = join6(legacyDir, "context.db");
153325
- if (!existsSync11(legacyDbPath))
153467
+ if (!existsSync12(legacyDbPath))
153326
153468
  return;
153327
153469
  log(`[magic-context] migrating legacy plugin storage: ${legacyDir} -> ${targetDbDir} (legacy left in place as backup)`);
153328
153470
  ensureSecureStorageDir(targetDbDir);
@@ -153339,7 +153481,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
153339
153481
  for (const suffix of ["", "-wal", "-shm"]) {
153340
153482
  const src = `${legacyDbPath}${suffix}`;
153341
153483
  const dst = join6(targetDbDir, `context.db${suffix}`);
153342
- if (existsSync11(src)) {
153484
+ if (existsSync12(src)) {
153343
153485
  try {
153344
153486
  copyFileSync(src, dst);
153345
153487
  } catch (error51) {
@@ -153349,7 +153491,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
153349
153491
  }
153350
153492
  const legacyModelsDir = join6(legacyDir, "models");
153351
153493
  const targetModelsDir = join6(targetDbDir, "models");
153352
- if (existsSync11(legacyModelsDir) && !existsSync11(targetModelsDir)) {
153494
+ if (existsSync12(legacyModelsDir) && !existsSync12(targetModelsDir)) {
153353
153495
  try {
153354
153496
  cpSync(legacyModelsDir, targetModelsDir, { recursive: true });
153355
153497
  } catch (error51) {
@@ -153949,6 +154091,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
153949
154091
  force_emergency_bypass_used INTEGER NOT NULL DEFAULT 0,
153950
154092
  emergency_drain_active INTEGER NOT NULL DEFAULT 0,
153951
154093
  historian_drain_failure_at INTEGER NOT NULL DEFAULT 0,
154094
+ wrapup_in_progress_state TEXT,
153952
154095
  cached_m0_materialized_at INTEGER,
153953
154096
  cached_m0_session_facts_version INTEGER,
153954
154097
  cached_m0_upgrade_state TEXT,
@@ -154237,6 +154380,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
154237
154380
  ensureColumn(db, "session_meta", "force_emergency_bypass_used", "INTEGER NOT NULL DEFAULT 0");
154238
154381
  ensureColumn(db, "session_meta", "emergency_drain_active", "INTEGER NOT NULL DEFAULT 0");
154239
154382
  ensureColumn(db, "session_meta", "historian_drain_failure_at", "INTEGER NOT NULL DEFAULT 0");
154383
+ ensureColumn(db, "session_meta", "wrapup_in_progress_state", "TEXT");
154240
154384
  ensureColumn(db, "session_meta", "cached_m0_materialized_at", "INTEGER");
154241
154385
  ensureColumn(db, "session_meta", "cached_m0_session_facts_version", "INTEGER");
154242
154386
  ensureColumn(db, "session_meta", "cached_m0_upgrade_state", "TEXT");
@@ -154415,7 +154559,7 @@ function getDatabasePersistenceError(db) {
154415
154559
  return null;
154416
154560
  return persistenceErrorByDatabase.get(db) ?? null;
154417
154561
  }
154418
- var databases, persistenceByDatabase, persistenceErrorByDatabase, pathByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 49, PERMISSIONS_ENFORCEABLE, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS = 120000;
154562
+ var databases, persistenceByDatabase, persistenceErrorByDatabase, pathByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 50, PERMISSIONS_ENFORCEABLE, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS = 120000;
154419
154563
  var init_storage_db = __esm(async () => {
154420
154564
  init_data_path();
154421
154565
  init_logger();
@@ -154755,6 +154899,147 @@ function resetProtectedTailNoEligibleHead(db, sessionId) {
154755
154899
  db.prepare("UPDATE session_meta SET recovery_no_eligible_head_count = 0 WHERE session_id = ?").run(sessionId);
154756
154900
  })();
154757
154901
  }
154902
+ function parseWrapupState(value) {
154903
+ if (typeof value !== "string" || value.trim().length === 0)
154904
+ return null;
154905
+ try {
154906
+ const parsed = JSON.parse(value);
154907
+ if (!parsed || typeof parsed !== "object")
154908
+ return null;
154909
+ if (typeof parsed.holderId !== "string" || parsed.holderId.length === 0)
154910
+ return null;
154911
+ const numberFields = [
154912
+ "acquiredAt",
154913
+ "expiresAt",
154914
+ "messagesToKeep",
154915
+ "anchorRawMessageCount",
154916
+ "targetEligibleEndOrdinal",
154917
+ "lastCompartmentEnd",
154918
+ "chunkIndex",
154919
+ "expectedChunks",
154920
+ "updatedAt"
154921
+ ];
154922
+ for (const field of numberFields) {
154923
+ if (typeof parsed[field] !== "number" || !Number.isFinite(parsed[field]))
154924
+ return null;
154925
+ }
154926
+ return parsed;
154927
+ } catch {
154928
+ return null;
154929
+ }
154930
+ }
154931
+ function readRawWrapupState(db, sessionId) {
154932
+ const row = db.prepare("SELECT wrapup_in_progress_state FROM session_meta WHERE session_id = ?").get(sessionId);
154933
+ return parseWrapupState(row?.wrapup_in_progress_state);
154934
+ }
154935
+ function getWrapupInProgressState(db, sessionId, now = Date.now()) {
154936
+ const state = readRawWrapupState(db, sessionId);
154937
+ if (!state)
154938
+ return null;
154939
+ if (state.expiresAt > now)
154940
+ return state;
154941
+ try {
154942
+ db.exec("BEGIN IMMEDIATE");
154943
+ } catch {
154944
+ return null;
154945
+ }
154946
+ let finished = false;
154947
+ try {
154948
+ const current = readRawWrapupState(db, sessionId);
154949
+ if (current && current.expiresAt <= now) {
154950
+ db.prepare("UPDATE session_meta SET wrapup_in_progress_state = NULL WHERE session_id = ?").run(sessionId);
154951
+ }
154952
+ db.exec("COMMIT");
154953
+ finished = true;
154954
+ } finally {
154955
+ if (!finished) {
154956
+ try {
154957
+ db.exec("ROLLBACK");
154958
+ } catch {}
154959
+ }
154960
+ }
154961
+ return null;
154962
+ }
154963
+ function isWrapupInProgress(db, sessionId, now = Date.now()) {
154964
+ return getWrapupInProgressState(db, sessionId, now) !== null;
154965
+ }
154966
+ function acquireWrapupInProgress(db, sessionId, state, now = Date.now()) {
154967
+ const acquiredAt = now;
154968
+ const next = {
154969
+ ...state,
154970
+ acquiredAt,
154971
+ expiresAt: acquiredAt + WRAPUP_IN_PROGRESS_TTL_MS,
154972
+ updatedAt: acquiredAt
154973
+ };
154974
+ db.exec("BEGIN IMMEDIATE");
154975
+ let finished = false;
154976
+ try {
154977
+ ensureSessionMetaRow(db, sessionId);
154978
+ const current = readRawWrapupState(db, sessionId);
154979
+ if (current && current.expiresAt > now && current.holderId !== state.holderId) {
154980
+ db.exec("COMMIT");
154981
+ finished = true;
154982
+ return { ok: false, state: current };
154983
+ }
154984
+ db.prepare("UPDATE session_meta SET wrapup_in_progress_state = ? WHERE session_id = ?").run(stableStringify(next), sessionId);
154985
+ db.exec("COMMIT");
154986
+ finished = true;
154987
+ return { ok: true, state: next };
154988
+ } finally {
154989
+ if (!finished) {
154990
+ try {
154991
+ db.exec("ROLLBACK");
154992
+ } catch {}
154993
+ }
154994
+ }
154995
+ }
154996
+ function updateWrapupInProgress(db, sessionId, holderId, updates, now = Date.now()) {
154997
+ db.exec("BEGIN IMMEDIATE");
154998
+ let finished = false;
154999
+ try {
155000
+ const current = readRawWrapupState(db, sessionId);
155001
+ if (!current || current.holderId !== holderId || current.expiresAt <= now) {
155002
+ db.exec("ROLLBACK");
155003
+ finished = true;
155004
+ return null;
155005
+ }
155006
+ const next = {
155007
+ ...current,
155008
+ ...updates,
155009
+ holderId,
155010
+ expiresAt: now + WRAPUP_IN_PROGRESS_TTL_MS,
155011
+ updatedAt: now
155012
+ };
155013
+ db.prepare("UPDATE session_meta SET wrapup_in_progress_state = ? WHERE session_id = ?").run(stableStringify(next), sessionId);
155014
+ db.exec("COMMIT");
155015
+ finished = true;
155016
+ return next;
155017
+ } finally {
155018
+ if (!finished) {
155019
+ try {
155020
+ db.exec("ROLLBACK");
155021
+ } catch {}
155022
+ }
155023
+ }
155024
+ }
155025
+ function releaseWrapupInProgress(db, sessionId, holderId) {
155026
+ db.exec("BEGIN IMMEDIATE");
155027
+ let finished = false;
155028
+ try {
155029
+ const current = readRawWrapupState(db, sessionId);
155030
+ if (current?.holderId === holderId) {
155031
+ db.prepare("UPDATE session_meta SET wrapup_in_progress_state = NULL WHERE session_id = ?").run(sessionId);
155032
+ }
155033
+ db.exec("COMMIT");
155034
+ finished = true;
155035
+ } finally {
155036
+ if (!finished) {
155037
+ try {
155038
+ db.exec("ROLLBACK");
155039
+ } catch {}
155040
+ }
155041
+ }
155042
+ }
154758
155043
  function protectedTailWindowBudget(usagePercentage, usable, perRunCap) {
154759
155044
  if (usagePercentage >= 95)
154760
155045
  return Math.min(1e6, Math.max(4 * perRunCap, Math.round(0.5 * usable)));
@@ -155432,7 +155717,7 @@ function setSessionWorkMetrics(db, sessionId, newWorkTokens, totalInputTokens) {
155432
155717
  SET new_work_tokens = ?, total_input_tokens = ?
155433
155718
  WHERE session_id = ?`).run(Math.max(0, Math.floor(newWorkTokens)), Math.max(0, Math.floor(totalInputTokens)), sessionId);
155434
155719
  }
155435
- var CAS_RETRY_LIMIT = 5, AUTO_SEARCH_NO_HINT_REASONS, DEFAULT_PROTECTED_TAIL_META, DRAIN_WINDOW_MS, EMERGENCY_DRAIN_ENTER_PERCENTAGE = 95, EMERGENCY_DRAIN_EXIT_MARGIN = 10, EMERGENCY_DRAIN_FALLBACK_EXIT_PERCENTAGE = 55, EMERGENCY_DRAIN_FAILURE_BACKOFF_MS = 60000, EMERGENCY_DRAIN_MAX_LATCH_MS;
155720
+ var CAS_RETRY_LIMIT = 5, AUTO_SEARCH_NO_HINT_REASONS, DEFAULT_PROTECTED_TAIL_META, DRAIN_WINDOW_MS, WRAPUP_IN_PROGRESS_TTL_MS, EMERGENCY_DRAIN_ENTER_PERCENTAGE = 95, EMERGENCY_DRAIN_EXIT_MARGIN = 10, EMERGENCY_DRAIN_FALLBACK_EXIT_PERCENTAGE = 55, EMERGENCY_DRAIN_FAILURE_BACKOFF_MS = 60000, EMERGENCY_DRAIN_MAX_LATCH_MS;
155436
155721
  var init_storage_meta_persisted = __esm(() => {
155437
155722
  init_logger();
155438
155723
  init_storage_meta_shared();
@@ -155456,6 +155741,7 @@ var init_storage_meta_persisted = __esm(() => {
155456
155741
  historianDrainFailureAt: 0
155457
155742
  };
155458
155743
  DRAIN_WINDOW_MS = 10 * 60 * 1000;
155744
+ WRAPUP_IN_PROGRESS_TTL_MS = 5 * 60 * 1000;
155459
155745
  EMERGENCY_DRAIN_MAX_LATCH_MS = 30 * 60 * 1000;
155460
155746
  });
155461
155747
 
@@ -165860,7 +166146,7 @@ __export(exports_conflict_warning_hook, {
165860
166146
  sendConflictWarning: () => sendConflictWarning,
165861
166147
  cleanupConflictWarnings: () => cleanupConflictWarnings
165862
166148
  });
165863
- import { existsSync as existsSync15, readFileSync as readFileSync10 } from "node:fs";
166149
+ import { existsSync as existsSync16, readFileSync as readFileSync10 } from "node:fs";
165864
166150
  import { homedir as homedir7, platform as platform2 } from "node:os";
165865
166151
  import { join as join13 } from "node:path";
165866
166152
  function getDesktopStatePath() {
@@ -165881,7 +166167,7 @@ function getDesktopStatePath() {
165881
166167
  }
165882
166168
  function readDesktopState(directory) {
165883
166169
  const statePath = getDesktopStatePath();
165884
- if (!statePath || !existsSync15(statePath)) {
166170
+ if (!statePath || !existsSync16(statePath)) {
165885
166171
  log(`[magic-context] conflict-warning: Desktop state file not found at ${statePath}`);
165886
166172
  return { sessionId: null, sidecarUrl: null };
165887
166173
  }
@@ -167057,7 +167343,8 @@ function isNativeRuntimeMissingError(error51) {
167057
167343
  if (code === "ERR_DLOPEN_FAILED" && lower.includes("onnxruntime")) {
167058
167344
  return true;
167059
167345
  }
167060
- if (!lower.includes("onnxruntime-node"))
167346
+ const mentionsNativeRuntime = lower.includes("onnxruntime-node") || lower.includes("onnxruntime_binding");
167347
+ if (!mentionsNativeRuntime)
167061
167348
  return false;
167062
167349
  return code === "ERR_MODULE_NOT_FOUND" || name2 === "ResolveMessage" || lower.includes("cannot find package") || lower.includes("cannot find module") || lower.includes("err_module_not_found");
167063
167350
  }
@@ -167202,7 +167489,7 @@ class LocalEmbeddingProvider {
167202
167489
  } catch (error51) {
167203
167490
  if (isNativeRuntimeMissingError(error51)) {
167204
167491
  nativeRuntimeMissing = true;
167205
- 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.");
167492
+ 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.");
167206
167493
  } else {
167207
167494
  log("[magic-context] embedding model failed to load:", error51);
167208
167495
  }
@@ -169786,7 +170073,7 @@ function getMemoryVerifications(db, memoryIds) {
169786
170073
  var MEMORY_VERIFICATION_SENTINEL = "";
169787
170074
  // src/features/magic-context/memory/verification-paths.ts
169788
170075
  import { execFile } from "node:child_process";
169789
- import { existsSync as existsSync16, realpathSync as realpathSync2, statSync as statSync5 } from "node:fs";
170076
+ import { existsSync as existsSync17, realpathSync as realpathSync3, statSync as statSync5 } from "node:fs";
169790
170077
  import path6 from "node:path";
169791
170078
  import { promisify } from "node:util";
169792
170079
  async function runGit(cwd, args) {
@@ -169811,7 +170098,7 @@ function isWithin(root, candidate) {
169811
170098
  }
169812
170099
  function safeRealpath(value) {
169813
170100
  try {
169814
- return realpathSync2.native(value);
170101
+ return realpathSync3.native(value);
169815
170102
  } catch {
169816
170103
  return null;
169817
170104
  }
@@ -169889,7 +170176,7 @@ function verificationFileExists(baseRoot, filePath) {
169889
170176
  return false;
169890
170177
  const root = path6.resolve(baseRoot);
169891
170178
  const candidate = path6.resolve(root, filePath);
169892
- return isWithin(root, candidate) && existsSync16(candidate);
170179
+ return isWithin(root, candidate) && existsSync17(candidate);
169893
170180
  }
169894
170181
  async function normalizeVerificationFiles(args) {
169895
170182
  const cwd = path6.resolve(args.cwd);
@@ -169922,7 +170209,7 @@ async function normalizeVerificationFiles(args) {
169922
170209
  warnings.push(`Skipped verification path "${value}" because it is the repo/project root.`);
169923
170210
  continue;
169924
170211
  }
169925
- if (existsSync16(candidate)) {
170212
+ if (existsSync17(candidate)) {
169926
170213
  try {
169927
170214
  if (statSync5(candidate).isDirectory()) {
169928
170215
  warnings.push(`Skipped verification path "${value}" because it is a directory.`);
@@ -169953,7 +170240,7 @@ async function normalizeVerificationFiles(args) {
169953
170240
  }
169954
170241
  normalized.push(tracked);
169955
170242
  } else {
169956
- if (!existsSync16(candidate)) {
170243
+ if (!existsSync17(candidate)) {
169957
170244
  warnings.push(`Skipped verification path "${value}" because it does not exist.`);
169958
170245
  continue;
169959
170246
  }
@@ -178941,7 +179228,8 @@ function readCurrentM0SnapshotMarkers(args) {
178941
179228
  sessionFactsVersion: getSessionFactsVersion(args.db, args.sessionId),
178942
179229
  upgradeState: getUpgradeState(args.db, args.sessionId),
178943
179230
  systemHash: hard.systemHash,
178944
- modelKey: hard.modelKey
179231
+ modelKey: hard.modelKey,
179232
+ projectIdentity: args.projectPath ?? null
178945
179233
  };
178946
179234
  }
178947
179235
  function snapshotMarkersFromCachedM0(state) {
@@ -178974,7 +179262,8 @@ function snapshotMarkersFromCachedM0(state) {
178974
179262
  sessionFactsVersion: state.cachedM0SessionFactsVersion,
178975
179263
  upgradeState: state.cachedM0UpgradeState,
178976
179264
  systemHash: state.cachedM0SystemHash ?? "",
178977
- modelKey: state.cachedM0ModelKey ?? ""
179265
+ modelKey: state.cachedM0ModelKey ?? "",
179266
+ projectIdentity: state.cachedM0ProjectIdentity ?? null
178978
179267
  };
178979
179268
  }
178980
179269
  function mustMaterialize(args) {
@@ -178993,6 +179282,15 @@ function mustMaterialize(args) {
178993
179282
  if (hard.cacheExpired && hard.lastResponseTime > 0 && hard.lastResponseTime > (args.state.cachedM0MaterializedAt ?? 0)) {
178994
179283
  return { value: true, reason: "ttl_idle" };
178995
179284
  }
179285
+ if (current.projectIdentity !== null) {
179286
+ const cachedProjectIdentity = args.state.cachedM0ProjectIdentity ?? null;
179287
+ if (cachedProjectIdentity === null) {
179288
+ args.state.cachedM0ProjectIdentity = current.projectIdentity;
179289
+ args.db.prepare("UPDATE session_meta SET cached_m0_project_identity = ? WHERE session_id = ?").run(current.projectIdentity, args.sessionId);
179290
+ } else if (cachedProjectIdentity !== current.projectIdentity) {
179291
+ return { value: true, reason: "project_change" };
179292
+ }
179293
+ }
178996
179294
  if (current.workspaceFingerprint !== null || (args.state.cachedM0WorkspaceFingerprint ?? null) !== null) {
178997
179295
  if ((args.state.cachedM0WorkspaceFingerprint ?? null) !== current.workspaceFingerprint) {
178998
179296
  return { value: true, reason: "project_memory_epoch" };
@@ -179233,6 +179531,7 @@ function applyMarkersToState(state, m0Bytes, markers, m1Bytes) {
179233
179531
  state.cachedM0UpgradeState = markers.upgradeState;
179234
179532
  state.cachedM0SystemHash = markers.systemHash;
179235
179533
  state.cachedM0ModelKey = markers.modelKey;
179534
+ state.cachedM0ProjectIdentity = markers.projectIdentity;
179236
179535
  state.snapshotMarkers = markers;
179237
179536
  }
179238
179537
  function historySliceTokens(m0Text) {
@@ -179356,10 +179655,11 @@ function materializeM0(options) {
179356
179655
  sessionFactsVersion: getSessionFactsVersion(options.db, options.sessionId),
179357
179656
  upgradeState: getUpgradeState(options.db, options.sessionId),
179358
179657
  systemHash: snapshotMarkers.systemHash,
179359
- modelKey: snapshotMarkers.modelKey
179658
+ modelKey: snapshotMarkers.modelKey,
179659
+ projectIdentity: projectPath ?? null
179360
179660
  };
179361
179661
  const memoryEpochStale = current.workspaceFingerprint !== null || snapshotMarkers.workspaceFingerprint !== null ? current.workspaceFingerprint !== snapshotMarkers.workspaceFingerprint : current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch;
179362
- const stale = memoryEpochStale || current.projectUserProfileVersion !== snapshotMarkers.projectUserProfileVersion || current.maxCompartmentSeq !== snapshotMarkers.maxCompartmentSeq || current.maxMutationId !== snapshotMarkers.maxMutationId || current.maxMemoryMutationId !== snapshotMarkers.maxMemoryMutationId || current.sessionFactsVersion !== snapshotMarkers.sessionFactsVersion || current.upgradeState !== snapshotMarkers.upgradeState;
179662
+ const stale = memoryEpochStale || current.projectUserProfileVersion !== snapshotMarkers.projectUserProfileVersion || current.maxCompartmentSeq !== snapshotMarkers.maxCompartmentSeq || current.maxMutationId !== snapshotMarkers.maxMutationId || current.maxMemoryMutationId !== snapshotMarkers.maxMemoryMutationId || current.sessionFactsVersion !== snapshotMarkers.sessionFactsVersion || current.upgradeState !== snapshotMarkers.upgradeState || (current.projectIdentity ?? null) !== (snapshotMarkers.projectIdentity ?? null);
179363
179663
  if (stale) {
179364
179664
  options.db.exec("ROLLBACK");
179365
179665
  throw new MaterializeContentionError({ reason: "snapshot changed before Phase 3" });
@@ -179388,7 +179688,8 @@ function materializeM0(options) {
179388
179688
  sessionFactsVersion: snapshotMarkers.sessionFactsVersion,
179389
179689
  upgradeState: snapshotMarkers.upgradeState,
179390
179690
  systemHash: snapshotMarkers.systemHash,
179391
- modelKey: snapshotMarkers.modelKey
179691
+ modelKey: snapshotMarkers.modelKey,
179692
+ projectIdentity: snapshotMarkers.projectIdentity
179392
179693
  });
179393
179694
  options.db.prepare("UPDATE session_meta SET memory_block_count = ?, memory_block_ids = ? WHERE session_id = ?").run(renderedMemoryIds.length, JSON.stringify(renderedMemoryIds), options.sessionId);
179394
179695
  const baselineEndMessageId = lastCompartmentBoundaryId(compartments);
@@ -179546,6 +179847,7 @@ function readCachedM0M1Row(db, sessionId) {
179546
179847
  cached_m0_upgrade_state,
179547
179848
  cached_m0_system_hash,
179548
179849
  cached_m0_model_key,
179850
+ cached_m0_project_identity,
179549
179851
  memory_block_ids
179550
179852
  FROM session_meta
179551
179853
  WHERE session_id = ?`).get(sessionId);
@@ -179580,11 +179882,12 @@ function markersFromCachedRow(row) {
179580
179882
  sessionFactsVersion: row.cached_m0_session_facts_version,
179581
179883
  upgradeState: row.cached_m0_upgrade_state,
179582
179884
  systemHash: row.cached_m0_system_hash ?? "",
179583
- modelKey: row.cached_m0_model_key ?? ""
179885
+ modelKey: row.cached_m0_model_key ?? "",
179886
+ projectIdentity: row.cached_m0_project_identity ?? null
179584
179887
  };
179585
179888
  }
179586
179889
  function cachedRowMatchesState(row, state) {
179587
- return bufferEqualsNullable(row.cached_m0_bytes, state.cachedM0Bytes) && row.cached_m0_project_memory_epoch === state.cachedM0ProjectMemoryEpoch && (row.cached_m0_workspace_fingerprint ?? null) === (state.cachedM0WorkspaceFingerprint ?? null) && row.cached_m0_project_user_profile_version === state.cachedM0ProjectUserProfileVersion && row.cached_m0_max_compartment_seq === state.cachedM0MaxCompartmentSeq && row.cached_m0_max_memory_id === state.cachedM0MaxMemoryId && row.cached_m0_max_mutation_id === state.cachedM0MaxMutationId && row.cached_m0_max_memory_mutation_id === state.cachedM0MaxMemoryMutationId && row.cached_m0_materialized_at === state.cachedM0MaterializedAt && row.cached_m0_session_facts_version === state.cachedM0SessionFactsVersion && (row.cached_m0_upgrade_state ?? null) === (state.cachedM0UpgradeState ?? null) && (row.cached_m0_system_hash ?? "") === (state.cachedM0SystemHash ?? "") && (row.cached_m0_model_key ?? "") === (state.cachedM0ModelKey ?? "");
179890
+ return bufferEqualsNullable(row.cached_m0_bytes, state.cachedM0Bytes) && row.cached_m0_project_memory_epoch === state.cachedM0ProjectMemoryEpoch && (row.cached_m0_workspace_fingerprint ?? null) === (state.cachedM0WorkspaceFingerprint ?? null) && row.cached_m0_project_user_profile_version === state.cachedM0ProjectUserProfileVersion && row.cached_m0_max_compartment_seq === state.cachedM0MaxCompartmentSeq && row.cached_m0_max_memory_id === state.cachedM0MaxMemoryId && row.cached_m0_max_mutation_id === state.cachedM0MaxMutationId && row.cached_m0_max_memory_mutation_id === state.cachedM0MaxMemoryMutationId && row.cached_m0_materialized_at === state.cachedM0MaterializedAt && row.cached_m0_session_facts_version === state.cachedM0SessionFactsVersion && (row.cached_m0_upgrade_state ?? null) === (state.cachedM0UpgradeState ?? null) && (row.cached_m0_system_hash ?? "") === (state.cachedM0SystemHash ?? "") && (row.cached_m0_model_key ?? "") === (state.cachedM0ModelKey ?? "") && (row.cached_m0_project_identity ?? null) === (state.cachedM0ProjectIdentity ?? null);
179588
179891
  }
179589
179892
  function applyCachedRowToState(state, row) {
179590
179893
  const markers = markersFromCachedRow(row);
@@ -179606,6 +179909,7 @@ function applyCachedRowToState(state, row) {
179606
179909
  state.cachedM0UpgradeState = markers.upgradeState;
179607
179910
  state.cachedM0SystemHash = markers.systemHash;
179608
179911
  state.cachedM0ModelKey = markers.modelKey;
179912
+ state.cachedM0ProjectIdentity = markers.projectIdentity;
179609
179913
  state.snapshotMarkers = markers;
179610
179914
  }
179611
179915
  function replayCachedM1(state) {
@@ -180600,6 +180904,43 @@ function semanticSnapBoundary(args) {
180600
180904
  }
180601
180905
  return snapped;
180602
180906
  }
180907
+ function snapWrapupBoundaryToUser(args) {
180908
+ const { messages, index, candidate, offset, triggerBudget } = args;
180909
+ if (candidate <= offset)
180910
+ return candidate;
180911
+ const snapTokenLimit = Math.min(Math.max(triggerBudget, 2000), 48000);
180912
+ for (let ordinal = candidate;ordinal >= offset; ordinal -= 1) {
180913
+ const message = messages.find((m) => m.ordinal === ordinal);
180914
+ if (!message)
180915
+ continue;
180916
+ if (message.role !== "user" || !hasMeaningfulUserText(message.parts))
180917
+ continue;
180918
+ const extraTokens = index.rangeTokens(ordinal, candidate);
180919
+ if (extraTokens <= snapTokenLimit)
180920
+ return ordinal;
180921
+ return candidate;
180922
+ }
180923
+ return candidate;
180924
+ }
180925
+ function fenceWrapupBoundaryForToolArcs(args) {
180926
+ let boundary = args.candidate;
180927
+ const maxPasses = args.arcs.length + 1;
180928
+ for (let pass = 0;pass < maxPasses; pass += 1) {
180929
+ let next = boundary;
180930
+ for (const arc of args.arcs) {
180931
+ if (arc.resOrdinal === null) {
180932
+ continue;
180933
+ }
180934
+ if (arc.invOrdinal >= args.lastCompartmentEndOrdinal + 1 && arc.invOrdinal < next && next <= arc.resOrdinal) {
180935
+ next = arc.invOrdinal;
180936
+ }
180937
+ }
180938
+ if (next === boundary)
180939
+ return boundary;
180940
+ boundary = next;
180941
+ }
180942
+ return boundary;
180943
+ }
180603
180944
  function applyHeadCap(args) {
180604
180945
  const { index, protectedTailStart, offset, arcs, capTokens, recentOpenArcCutoff } = args;
180605
180946
  if (offset >= protectedTailStart)
@@ -180855,6 +181196,124 @@ function resolveBoundaryContext(args) {
180855
181196
  function resolveOpenCodeProtectedTailBoundary(args) {
180856
181197
  return resolveProtectedTailBoundary(resolveBoundaryContext(args));
180857
181198
  }
181199
+ function resolveWrapupProtectedTailBoundary(args) {
181200
+ const ctx = resolveBoundaryContext({ ...args, mode: "manual-wrapup" });
181201
+ const createdAt = ctx.createdAt ?? Date.now();
181202
+ const messages = readRawSessionMessages(ctx.sessionId);
181203
+ const absoluteMessageCount = getCachedAbsoluteMessageCount(ctx.sessionId) ?? undefined;
181204
+ const index = buildTrueRawTokenIndex(ctx.sessionId, messages, {
181205
+ providerShapeVersion: ctx.providerShapeVersion,
181206
+ cacheNamespace: ctx.cacheNamespace,
181207
+ absoluteMessageCount,
181208
+ storedTotalForMessage: ctx.storedTokenTotals ? (m) => {
181209
+ const value = ctx.storedTokenTotals?.get(m.id);
181210
+ return value === undefined ? null : value;
181211
+ } : undefined
181212
+ });
181213
+ const rawMessageCount = index.rawMessageCount;
181214
+ const offset = Math.max(1, ctx.lastCompartmentEndOrdinal + 1);
181215
+ const anchorRawMessageCount = Math.max(0, Math.min(rawMessageCount, Math.floor(args.anchorRawMessageCount ?? rawMessageCount)));
181216
+ const usagePercentage = clampPercentage(ctx.usage?.percentage ?? 0);
181217
+ const usageInputTokens = Math.max(0, Math.round(ctx.usage?.inputTokens ?? 0));
181218
+ const rawMessagesAboveLastCompartment = Math.max(0, anchorRawMessageCount - offset + 1);
181219
+ const keep = Math.max(1, Math.floor(args.messagesToKeep));
181220
+ let targetProtectedTailStart = offset;
181221
+ let boundaryReason = "manual-wrapup-empty";
181222
+ if (rawMessageCount === 0 || rawMessagesAboveLastCompartment <= keep) {
181223
+ targetProtectedTailStart = offset;
181224
+ boundaryReason = rawMessageCount === 0 ? "manual-wrapup-empty" : "manual-wrapup-within-keep";
181225
+ } else {
181226
+ targetProtectedTailStart = anchorRawMessageCount - keep + 1;
181227
+ boundaryReason = "manual-wrapup-keep-watermark";
181228
+ const arcs = buildToolArcs(messages);
181229
+ const fenced = fenceWrapupBoundaryForToolArcs({
181230
+ candidate: targetProtectedTailStart,
181231
+ arcs,
181232
+ lastCompartmentEndOrdinal: ctx.lastCompartmentEndOrdinal
181233
+ });
181234
+ if (fenced !== targetProtectedTailStart)
181235
+ boundaryReason = "manual-wrapup-tool-arc";
181236
+ targetProtectedTailStart = fenced;
181237
+ const snapped = snapWrapupBoundaryToUser({
181238
+ messages,
181239
+ index,
181240
+ candidate: targetProtectedTailStart,
181241
+ offset,
181242
+ triggerBudget: ctx.triggerBudget
181243
+ });
181244
+ if (snapped !== targetProtectedTailStart)
181245
+ boundaryReason = "manual-wrapup-user-snap";
181246
+ targetProtectedTailStart = snapped;
181247
+ const refenced = fenceWrapupBoundaryForToolArcs({
181248
+ candidate: targetProtectedTailStart,
181249
+ arcs,
181250
+ lastCompartmentEndOrdinal: ctx.lastCompartmentEndOrdinal
181251
+ });
181252
+ if (refenced !== targetProtectedTailStart)
181253
+ boundaryReason = "manual-wrapup-tool-arc";
181254
+ targetProtectedTailStart = refenced;
181255
+ }
181256
+ targetProtectedTailStart = clampOrdinal(targetProtectedTailStart, rawMessageCount);
181257
+ const target = deriveProtectedTailTokenTarget({
181258
+ contextLimit: ctx.contextLimit,
181259
+ executeThresholdPercentage: ctx.executeThresholdPercentage,
181260
+ usagePercentage,
181261
+ triggerBudget: ctx.triggerBudget
181262
+ });
181263
+ const perRunCap = selectPerRunCap({
181264
+ usagePercentage,
181265
+ N: target.N,
181266
+ contextLimit: ctx.contextLimit,
181267
+ executeThresholdPercentage: ctx.executeThresholdPercentage
181268
+ });
181269
+ const head = applyHeadCap({
181270
+ index,
181271
+ protectedTailStart: targetProtectedTailStart,
181272
+ offset,
181273
+ arcs: buildToolArcs(messages),
181274
+ lastCompartmentEndOrdinal: ctx.lastCompartmentEndOrdinal,
181275
+ capTokens: perRunCap,
181276
+ recentOpenArcCutoff: targetProtectedTailStart
181277
+ });
181278
+ const eligibleEndOrdinal = Math.min(head.eligibleEndOrdinal, targetProtectedTailStart);
181279
+ const rawRangeFingerprint = computeRawRangeFingerprint(messages, offset, eligibleEndOrdinal);
181280
+ const snapshot = {
181281
+ sessionId: ctx.sessionId,
181282
+ mode: "manual-wrapup",
181283
+ offset,
181284
+ offsetMessageId: boundaryMessageId(index, offset),
181285
+ protectedTailStart: targetProtectedTailStart,
181286
+ protectedTailStartMessageId: boundaryMessageId(index, targetProtectedTailStart),
181287
+ eligibleEndOrdinal,
181288
+ eligibleEndMessageId: boundaryMessageId(index, eligibleEndOrdinal - 1),
181289
+ rawMessageCountAtTrigger: rawMessageCount,
181290
+ rawLastMessageIdAtTrigger: boundaryMessageId(index, rawMessageCount),
181291
+ N: keep,
181292
+ usagePercentage,
181293
+ usageInputTokens,
181294
+ usageSource: ctx.usageSource,
181295
+ contextLimit: ctx.contextLimit,
181296
+ executeThresholdPercentage: ctx.executeThresholdPercentage,
181297
+ triggerBudget: ctx.triggerBudget,
181298
+ priorBoundaryOrdinal: ctx.priorBoundaryOrdinal,
181299
+ migrationFloorActive: ctx.migrationFloorActive,
181300
+ emergencyTailScale: ctx.emergencyTailScale,
181301
+ providerShapeVersion: ctx.providerShapeVersion,
181302
+ cacheNamespace: ctx.cacheNamespace,
181303
+ createdAt,
181304
+ rawRangeFingerprint,
181305
+ trueRawEligibleTokens: index.rangeTokens(offset, targetProtectedTailStart),
181306
+ oversizeAtomicUnit: head.oversizeAtomicUnit,
181307
+ boundaryReason
181308
+ };
181309
+ return {
181310
+ snapshot,
181311
+ rawMessagesAboveLastCompartment,
181312
+ anchorRawMessageCount,
181313
+ targetProtectedTailStart,
181314
+ targetEligibleEndOrdinal: targetProtectedTailStart
181315
+ };
181316
+ }
180858
181317
  function getRawHistoryEligibility(db, sessionId) {
180859
181318
  const lastCompartmentEnd = getLastCompartmentEndMessage(db, sessionId);
180860
181319
  const offset = Math.max(1, lastCompartmentEnd + 1);
@@ -183082,7 +183541,7 @@ async function runCompartmentAgent(deps) {
183082
183541
  currentContextLimit: deps.currentContextLimit ?? boundarySnapshot.contextLimit
183083
183542
  }) : { ok: true };
183084
183543
  if (!validation.ok && validation.reason === "stale_snapshot") {
183085
- const refreshed = resolveOpenCodeProtectedTailBoundary({
183544
+ const refreshed = deps.refreshBoundarySnapshot ? deps.refreshBoundarySnapshot(boundarySnapshot, validation) : resolveOpenCodeProtectedTailBoundary({
183086
183545
  db,
183087
183546
  sessionId,
183088
183547
  mode: "incremental-runner",
@@ -183095,7 +183554,7 @@ async function runCompartmentAgent(deps) {
183095
183554
  usageSource: boundarySnapshot.usageSource,
183096
183555
  emergencyTailScale: boundarySnapshot.emergencyTailScale
183097
183556
  });
183098
- if (hasRunnableCompartmentWindow(refreshed)) {
183557
+ if (refreshed && hasRunnableCompartmentWindow(refreshed)) {
183099
183558
  sessionLog(sessionId, `historian: refreshed stale protected-tail snapshot at run time (was: ${validation.detail ?? "stale"}) — eligible head ${refreshed.offset}-${refreshed.eligibleEndOrdinal - 1}`);
183100
183559
  boundarySnapshot = refreshed;
183101
183560
  validation = { ok: true };
@@ -183113,7 +183572,8 @@ async function runCompartmentAgent(deps) {
183113
183572
  if (protectedTailStart <= offset || eligibleEndOrdinal <= offset) {
183114
183573
  sessionLog(sessionId, `historian no-op: protectedTailStart=${protectedTailStart} eligibleEnd=${eligibleEndOrdinal} <= offset=${offset} — nothing to compact`);
183115
183574
  if (boundarySnapshot.usagePercentage < 80 && !boundarySnapshot.emergencyTailScale) {
183116
- clearEmergencyRecovery(db, sessionId);
183575
+ if (!isWrapupInProgress(db, sessionId))
183576
+ clearEmergencyRecovery(db, sessionId);
183117
183577
  } else {
183118
183578
  const count = recordHighPressureNoEligibleHead(db, boundarySnapshot);
183119
183579
  sessionLog(sessionId, `historian high-pressure no-op: recovery remains armed (noEligibleHeadCount=${count})`);
@@ -183126,7 +183586,7 @@ async function runCompartmentAgent(deps) {
183126
183586
  }
183127
183587
  const perRunCap = selectPerRunCap(boundarySnapshot);
183128
183588
  const usable = Math.max(1, Math.round(boundarySnapshot.contextLimit * boundarySnapshot.executeThresholdPercentage / 100));
183129
- const reserve = reserveProtectedTailDrainTokens({
183589
+ const reserve = deps.forceDrainQuota ? { ok: true, reservation: null } : reserveProtectedTailDrainTokens({
183130
183590
  db,
183131
183591
  sessionId,
183132
183592
  runId: crypto.randomUUID(),
@@ -183144,12 +183604,14 @@ async function runCompartmentAgent(deps) {
183144
183604
  }
183145
183605
  drainReservation = reserve.reservation;
183146
183606
  const chunk = readSessionChunk(sessionId, historianChunkTokens, offset, eligibleEndOrdinal);
183607
+ const forceKeepLastCompartmentForChunk = deps.forceKeepLastCompartment === true && !chunk.hasMore;
183147
183608
  telemetry.chunkStartOrdinal = chunk.startIndex;
183148
183609
  telemetry.chunkEndOrdinal = chunk.endIndex;
183149
183610
  if (!chunk.text || chunk.messageCount === 0) {
183150
183611
  sessionLog(sessionId, `historian no-op: chunk empty after filtering (messageCount=${chunk.messageCount}, textLen=${chunk.text?.length ?? 0}) range=${offset}-${eligibleEndOrdinal - 1}`);
183151
183612
  if (boundarySnapshot.usagePercentage < 80 && !boundarySnapshot.emergencyTailScale) {
183152
- clearEmergencyRecovery(db, sessionId);
183613
+ if (!isWrapupInProgress(db, sessionId))
183614
+ clearEmergencyRecovery(db, sessionId);
183153
183615
  } else {
183154
183616
  recordHighPressureNoEligibleHead(db, boundarySnapshot);
183155
183617
  }
@@ -183223,7 +183685,7 @@ ${chunkText}`,
183223
183685
  const BOUNDARY_HEALING_SLACK = 2;
183224
183686
  const inEmergency = getOverflowState(db, sessionId).needsEmergencyRecovery;
183225
183687
  let persistedCompartments = emittedCompartments;
183226
- if (!inEmergency && emittedCompartments.length >= 2) {
183688
+ if (!inEmergency && !forceKeepLastCompartmentForChunk && emittedCompartments.length >= 2) {
183227
183689
  const lastEmitted = emittedCompartments[emittedCompartments.length - 1];
183228
183690
  const lookaheadMargin = chunk.endIndex - lastEmitted.endMessage;
183229
183691
  if (lookaheadMargin <= BOUNDARY_HEALING_SLACK) {
@@ -183247,10 +183709,21 @@ ${chunkText}`,
183247
183709
  const lastNewEndMessageId = newCompartments[newCompartments.length - 1]?.endMessageId;
183248
183710
  const promotionDirectory = sessionDirectory || deps.directory;
183249
183711
  const discardedLast = persistedCompartments.length < emittedCompartments.length;
183712
+ const weakLookaheadFinalCompartment = forceKeepLastCompartmentForChunk;
183713
+ const skipUnanchoredPromotion = discardedLast || weakLookaheadFinalCompartment;
183250
183714
  const embeddingActive = !!promotionDirectory && deps.memoryEnabled !== false;
183251
183715
  const promotionActive = embeddingActive && deps.autoPromote !== false;
183252
183716
  const promotionProjectIdentity = promotionDirectory ? resolveProjectIdentity(promotionDirectory) : "";
183253
- const publishableEvents = (validatedPass.events ?? []).filter((e) => e.atCompartment == null || e.atCompartment <= persistedCompartments.length);
183717
+ const publishableEvents = (validatedPass.events ?? []).filter((e) => {
183718
+ if (typeof e.atCompartment !== "number")
183719
+ return !weakLookaheadFinalCompartment;
183720
+ if (e.atCompartment > persistedCompartments.length)
183721
+ return false;
183722
+ if (weakLookaheadFinalCompartment && e.atCompartment >= emittedCompartments.length) {
183723
+ return false;
183724
+ }
183725
+ return true;
183726
+ });
183254
183727
  let promotedFactRefs = [];
183255
183728
  let persistedIds = [];
183256
183729
  const holderId = deps.compartmentLeaseHolderId;
@@ -183270,7 +183743,7 @@ ${chunkText}`,
183270
183743
  }
183271
183744
  appendCompartments(db, sessionId, persistedCompartments);
183272
183745
  persistedIds = getCompartments(db, sessionId).slice(-persistedCompartments.length).map((c) => c.id);
183273
- if (promotionActive && !discardedLast) {
183746
+ if (promotionActive && !skipUnanchoredPromotion) {
183274
183747
  promotedFactRefs = promoteSessionFactsDurable(db, sessionId, promotionProjectIdentity, validatedPass.facts ?? []);
183275
183748
  }
183276
183749
  if (publishableEvents.length > 0) {
@@ -183285,7 +183758,8 @@ ${chunkText}`,
183285
183758
  clearHistorianFailureState(db, sessionId);
183286
183759
  clearHistorianDrainFailure(db, sessionId);
183287
183760
  recordProtectedTailPublicationFloor(db, sessionId, lastCompartmentEnd + 1);
183288
- clearEmergencyRecovery(db, sessionId);
183761
+ if (!isWrapupInProgress(db, sessionId))
183762
+ clearEmergencyRecovery(db, sessionId);
183289
183763
  drainReservation = null;
183290
183764
  if (deferMarkerApplication && lastNewEndMessageId) {
183291
183765
  setPendingCompactionMarkerState(db, sessionId, {
@@ -183357,7 +183831,7 @@ ${chunkText}`,
183357
183831
  }
183358
183832
  })();
183359
183833
  }
183360
- if (deps.experimentalUserMemories === true && !discardedLast && validatedPass.userObservations && validatedPass.userObservations.length > 0) {
183834
+ if (deps.experimentalUserMemories === true && !skipUnanchoredPromotion && validatedPass.userObservations && validatedPass.userObservations.length > 0) {
183361
183835
  try {
183362
183836
  const lastNew = newCompartments[newCompartments.length - 1];
183363
183837
  insertUserMemoryCandidates(db, validatedPass.userObservations.map((obs) => ({
@@ -183371,7 +183845,7 @@ ${chunkText}`,
183371
183845
  sessionLog(sessionId, "failed to store user memory candidates:", error51);
183372
183846
  }
183373
183847
  }
183374
- if (!discardedLast && promotionProjectIdentity && validatedPass.primerCandidates && validatedPass.primerCandidates.length > 0) {
183848
+ if (!skipUnanchoredPromotion && promotionProjectIdentity && validatedPass.primerCandidates && validatedPass.primerCandidates.length > 0) {
183375
183849
  try {
183376
183850
  const firstNew = newCompartments[0];
183377
183851
  const lastNew = newCompartments[newCompartments.length - 1];
@@ -184238,6 +184712,21 @@ function markActiveCompartmentRunPublished(sessionId) {
184238
184712
  if (activeRun)
184239
184713
  activeRun.published = true;
184240
184714
  }
184715
+ function registerActiveCompartmentRun(sessionId, promise2, kind = "other") {
184716
+ const activeRun = {
184717
+ promise: Promise.resolve(),
184718
+ published: false,
184719
+ kind
184720
+ };
184721
+ const wrapped = promise2.finally(() => {
184722
+ if (activeRuns.get(sessionId)?.promise === wrapped) {
184723
+ activeRuns.delete(sessionId);
184724
+ }
184725
+ });
184726
+ activeRun.promise = wrapped;
184727
+ activeRuns.set(sessionId, activeRun);
184728
+ return activeRun;
184729
+ }
184241
184730
  function withPublishedCallback(deps) {
184242
184731
  return {
184243
184732
  ...deps,
@@ -184249,8 +184738,12 @@ function withPublishedCallback(deps) {
184249
184738
  }
184250
184739
  function startLeaseRenewal(deps, holderId) {
184251
184740
  return setInterval(() => {
184252
- if (!renewCompartmentLease(deps.db, deps.sessionId, holderId)) {
184253
- sessionLog(deps.sessionId, "compartment lease renewal failed; publish will be skipped if holder is stale");
184741
+ try {
184742
+ if (!renewCompartmentLease(deps.db, deps.sessionId, holderId)) {
184743
+ sessionLog(deps.sessionId, "compartment lease renewal failed; publish will be skipped if holder is stale");
184744
+ }
184745
+ } catch (err) {
184746
+ sessionLog(deps.sessionId, `compartment lease renewal threw; publish will be skipped if holder is stale (${err instanceof Error ? err.message : String(err)})`);
184254
184747
  }
184255
184748
  }, COMPARTMENT_LEASE_RENEWAL_MS);
184256
184749
  }
@@ -184259,6 +184752,11 @@ function startCompartmentAgent(deps) {
184259
184752
  if (existing) {
184260
184753
  return;
184261
184754
  }
184755
+ if (isWrapupInProgress(deps.db, deps.sessionId)) {
184756
+ sessionLog(deps.sessionId, "compartment agent skipped: /ctx-wrapup is active");
184757
+ updateSessionMeta(deps.db, deps.sessionId, { compartmentInProgress: false });
184758
+ return;
184759
+ }
184262
184760
  const holderId = crypto.randomUUID();
184263
184761
  const lease = acquireCompartmentLease(deps.db, deps.sessionId, holderId);
184264
184762
  if (!lease) {
@@ -184266,6 +184764,12 @@ function startCompartmentAgent(deps) {
184266
184764
  updateSessionMeta(deps.db, deps.sessionId, { compartmentInProgress: false });
184267
184765
  return;
184268
184766
  }
184767
+ if (isWrapupInProgress(deps.db, deps.sessionId)) {
184768
+ sessionLog(deps.sessionId, "compartment agent skipped: /ctx-wrapup became active");
184769
+ releaseCompartmentLease(deps.db, deps.sessionId, holderId);
184770
+ updateSessionMeta(deps.db, deps.sessionId, { compartmentInProgress: false });
184771
+ return;
184772
+ }
184269
184773
  const renewal = startLeaseRenewal(deps, holderId);
184270
184774
  let realRunStarted = false;
184271
184775
  const runnerDeps = withPublishedCallback({
@@ -184287,13 +184791,19 @@ function startCompartmentAgent(deps) {
184287
184791
  activeRuns.delete(deps.sessionId);
184288
184792
  }
184289
184793
  });
184290
- activeRuns.set(deps.sessionId, { promise: promise2, published: false });
184794
+ activeRuns.set(deps.sessionId, { promise: promise2, published: false, kind: "incremental" });
184291
184795
  if (!realRunStarted && activeRuns.get(deps.sessionId)?.promise === promise2) {
184292
184796
  activeRuns.delete(deps.sessionId);
184293
184797
  }
184294
184798
  }
184295
184799
  async function executeContextRecompWithResult(deps, options = {}) {
184296
184800
  const { sessionId } = deps;
184801
+ if (isWrapupInProgress(deps.db, sessionId)) {
184802
+ return {
184803
+ message: "## Magic Recomp — Skipped\n\n/ctx-wrapup is already compacting this session. Wait for it to finish, then try `/ctx-recomp` again.",
184804
+ published: false
184805
+ };
184806
+ }
184297
184807
  if (activeRuns.has(sessionId)) {
184298
184808
  return {
184299
184809
  message: "## Magic Recomp — Skipped\n\nHistorian is already running for this session. Wait for it to finish, then try `/ctx-recomp` again.",
@@ -184317,7 +184827,7 @@ async function executeContextRecompWithResult(deps, options = {}) {
184317
184827
  }).catch((err) => {
184318
184828
  sessionLog(sessionId, "compartment agent: recomp unhandled rejection:", err);
184319
184829
  });
184320
- activeRuns.set(sessionId, { promise: wrappedPromise, published: false });
184830
+ activeRuns.set(sessionId, { promise: wrappedPromise, published: false, kind: "recomp" });
184321
184831
  try {
184322
184832
  const message = await promise2;
184323
184833
  const published = activeRuns.get(sessionId)?.published === true;
@@ -184745,6 +185255,13 @@ Recomp crashed: ${String(error51)}`;
184745
185255
  }
184746
185256
  }
184747
185257
  async function runManagedUpgrade(ctx, sessionId) {
185258
+ if (isWrapupInProgress(ctx.db, sessionId)) {
185259
+ const message = "/ctx-wrapup is already compacting this session. Wait for it to finish, then try `/ctx-session-upgrade` again.";
185260
+ setRecompTerminal(ctx.liveSessionState, sessionId, "skipped", message);
185261
+ return `## Session Upgrade — Skipped
185262
+
185263
+ ${message}`;
185264
+ }
184748
185265
  setRecompStarting(ctx.liveSessionState, sessionId, "Starting upgrade…", "upgrade");
184749
185266
  try {
184750
185267
  const compartments = getCompartments(ctx.db, sessionId);
@@ -184899,13 +185416,14 @@ function shouldShowAnnouncement() {
184899
185416
  }
184900
185417
  return ordering > 0;
184901
185418
  }
184902
- var ANNOUNCEMENT_VERSION = "0.30.4", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
185419
+ var ANNOUNCEMENT_VERSION = "0.31.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
184903
185420
  var init_announcement = __esm(() => {
184904
185421
  init_data_path();
184905
185422
  ANNOUNCEMENT_FEATURES = [
184906
- "Pi: fixed historian and dreamer failing when your model comes from a provider extension (Antigravity, Anthropic OAuth, etc.). Subagents now load your extensions.",
184907
- "Pi: fixed the retrospective and refresh-primers dreamer tasks reading sessions through the wrong API.",
184908
- "OpenCode: the plugin no longer re-adds its TUI sidebar entry on startup, so removing it from tui.jsonc now sticks."
185423
+ "New /ctx-wrapup command: compact older history on demand, keeping the newest N messages raw. Run it before switching to a smaller-context model.",
185424
+ "ctx_search now also searches your session notes and smart notes.",
185425
+ "Project identity now survives transient git failures (slow disks, dubious ownership) without splitting your project memory.",
185426
+ "Removed the ctx_reduce_enabled setting; agent-controlled reduction is always on. Caveman text compression is now independent (caveman_text_compression.enabled)."
184909
185427
  ];
184910
185428
  });
184911
185429
 
@@ -186333,6 +186851,10 @@ function getMagicContextBuiltinCommands() {
186333
186851
  template: "ctx-recomp",
186334
186852
  description: "Rebuild compartments and facts from raw history (full or <start>-<end> range)"
186335
186853
  },
186854
+ "ctx-wrapup": {
186855
+ template: "ctx-wrapup",
186856
+ description: "Compact older live history while keeping the newest messages raw"
186857
+ },
186336
186858
  "ctx-session-upgrade": {
186337
186859
  template: "ctx-session-upgrade",
186338
186860
  description: "Upgrade this session to the latest history format: rebuild compartments and migrate project memories"
@@ -186692,7 +187214,7 @@ init_project_identity();
186692
187214
  init_logger();
186693
187215
  init_project_identity();
186694
187216
  init_session_project_storage();
186695
- import { existsSync as existsSync8 } from "node:fs";
187217
+ import { existsSync as existsSync9 } from "node:fs";
186696
187218
  var LEASE_TTL_MS = 10 * 60 * 1000;
186697
187219
  var SESSION_QUERY_CHUNK_SIZE = 250;
186698
187220
  var YIELD_EVERY_NEW_DIRECTORIES = 10;
@@ -186848,7 +187370,7 @@ async function runSessionProjectBackfill(db, sessions, options = {}) {
186848
187370
  result.skippedEmptyDirectories += 1;
186849
187371
  continue;
186850
187372
  }
186851
- const stillExists = existenceCache.get(session.directory) ?? existsSync8(session.directory);
187373
+ const stillExists = existenceCache.get(session.directory) ?? existsSync9(session.directory);
186852
187374
  existenceCache.set(session.directory, stillExists);
186853
187375
  if (!stillExists) {
186854
187376
  result.skippedDeadDirectories += 1;
@@ -187037,7 +187559,7 @@ await init_storage_db();
187037
187559
  init_logger();
187038
187560
  init_project_identity();
187039
187561
  import { createHash as createHash5 } from "node:crypto";
187040
- import { realpathSync } from "node:fs";
187562
+ import { realpathSync as realpathSync2 } from "node:fs";
187041
187563
  import path5 from "node:path";
187042
187564
 
187043
187565
  // src/features/magic-context/memory/relocate-memory.ts
@@ -187071,7 +187593,10 @@ function countFailures(db) {
187071
187593
  }
187072
187594
  function classifyBackfillError(error51) {
187073
187595
  if (error51 instanceof ProjectIdentityError) {
187074
- return { errorClass: error51.errorClass, errorMessage: error51.message };
187596
+ return {
187597
+ errorClass: error51.errorClass === "dubious_ownership" ? "unknown" : error51.errorClass,
187598
+ errorMessage: error51.message
187599
+ };
187075
187600
  }
187076
187601
  if (error51 instanceof Error) {
187077
187602
  return { errorClass: "unknown", errorMessage: error51.message };
@@ -187091,7 +187616,7 @@ function resolveBackfillIdentity(rawProjectPath) {
187091
187616
  function computeLegacyRustDirIdentity(rawProjectPath) {
187092
187617
  let canonical;
187093
187618
  try {
187094
- canonical = realpathSync(rawProjectPath);
187619
+ canonical = realpathSync2(rawProjectPath);
187095
187620
  } catch {
187096
187621
  canonical = path5.isAbsolute(rawProjectPath) ? rawProjectPath : path5.join(process.cwd(), rawProjectPath);
187097
187622
  }
@@ -187264,20 +187789,20 @@ async function runDeferredV22Backfill(db, options = {}) {
187264
187789
 
187265
187790
  // src/hooks/auto-update-checker/index.ts
187266
187791
  init_logger();
187267
- import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync9, renameSync as renameSync3, writeFileSync as writeFileSync5 } from "node:fs";
187792
+ import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync9, renameSync as renameSync3, writeFileSync as writeFileSync5 } from "node:fs";
187268
187793
  import { dirname as dirname7, join as join10 } from "node:path";
187269
187794
 
187270
187795
  // src/hooks/auto-update-checker/cache.ts
187271
187796
  init_logger();
187272
187797
  var import_comment_json2 = __toESM(require_src2(), 1);
187273
187798
  import { spawn } from "node:child_process";
187274
- import { existsSync as existsSync13, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
187799
+ import { existsSync as existsSync14, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
187275
187800
  import { basename as basename2, dirname as dirname6, join as join9 } from "node:path";
187276
187801
 
187277
187802
  // src/hooks/auto-update-checker/checker.ts
187278
187803
  init_logger();
187279
187804
  var import_comment_json = __toESM(require_src2(), 1);
187280
- import { existsSync as existsSync12, readFileSync as readFileSync7, renameSync as renameSync2, statSync as statSync4, writeFileSync as writeFileSync3 } from "node:fs";
187805
+ import { existsSync as existsSync13, readFileSync as readFileSync7, renameSync as renameSync2, statSync as statSync4, writeFileSync as writeFileSync3 } from "node:fs";
187281
187806
  import { homedir as homedir5 } from "node:os";
187282
187807
  import { dirname as dirname5, isAbsolute as isAbsolute3, join as join8, resolve as resolve2 } from "node:path";
187283
187808
  import { fileURLToPath } from "node:url";
@@ -187387,7 +187912,7 @@ function findPackageJsonUp(startPath) {
187387
187912
  let dir = stat.isDirectory() ? startPath : dirname5(startPath);
187388
187913
  for (let i = 0;i < 10; i++) {
187389
187914
  const pkgPath = join8(dir, "package.json");
187390
- if (existsSync12(pkgPath)) {
187915
+ if (existsSync13(pkgPath)) {
187391
187916
  try {
187392
187917
  const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync7(pkgPath, "utf-8")));
187393
187918
  if (pkg.success && pkg.data.name === PACKAGE_NAME)
@@ -187405,7 +187930,7 @@ function findPackageJsonUp(startPath) {
187405
187930
  function getLocalDevPath(directory) {
187406
187931
  for (const configPath of getConfigPaths(directory)) {
187407
187932
  try {
187408
- if (!existsSync12(configPath))
187933
+ if (!existsSync13(configPath))
187409
187934
  continue;
187410
187935
  const rawConfig = parseJsonConfig(readFileSync7(configPath, "utf-8"));
187411
187936
  const plugins = getPluginEntries(rawConfig);
@@ -187451,7 +187976,7 @@ function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
187451
187976
  function findPluginEntry(directory) {
187452
187977
  for (const configPath of getConfigPaths(directory)) {
187453
187978
  try {
187454
- if (!existsSync12(configPath))
187979
+ if (!existsSync13(configPath))
187455
187980
  continue;
187456
187981
  const rawConfig = parseJsonConfig(readFileSync7(configPath, "utf-8"));
187457
187982
  const plugins = getPluginEntries(rawConfig);
@@ -187489,7 +188014,7 @@ function getCachedVersion(spec) {
187489
188014
  ].filter(isString);
187490
188015
  for (const packageJsonPath of candidates) {
187491
188016
  try {
187492
- if (!existsSync12(packageJsonPath))
188017
+ if (!existsSync13(packageJsonPath))
187493
188018
  continue;
187494
188019
  const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync7(packageJsonPath, "utf-8")));
187495
188020
  if (pkg.success && pkg.data.version) {
@@ -187544,7 +188069,7 @@ function stripPackageNameFromPath(pathValue, packageName) {
187544
188069
  }
187545
188070
  function removeFromPackageLock(installDir, packageName) {
187546
188071
  const lockPath = join9(installDir, "package-lock.json");
187547
- if (!existsSync13(lockPath))
188072
+ if (!existsSync14(lockPath))
187548
188073
  return false;
187549
188074
  try {
187550
188075
  const lock = import_comment_json2.parse(readFileSync8(lockPath, "utf-8"));
@@ -187570,7 +188095,7 @@ function removeFromPackageLock(installDir, packageName) {
187570
188095
  }
187571
188096
  }
187572
188097
  function ensureDependencyVersion(packageJsonPath, packageName, version2) {
187573
- if (!existsSync13(packageJsonPath))
188098
+ if (!existsSync14(packageJsonPath))
187574
188099
  return false;
187575
188100
  try {
187576
188101
  const raw = import_comment_json2.parse(readFileSync8(packageJsonPath, "utf-8"));
@@ -187593,7 +188118,7 @@ function ensureDependencyVersion(packageJsonPath, packageName, version2) {
187593
188118
  }
187594
188119
  function removeInstalledPackage(installDir, packageName) {
187595
188120
  const packageDir = join9(installDir, "node_modules", packageName);
187596
- if (!existsSync13(packageDir))
188121
+ if (!existsSync14(packageDir))
187597
188122
  return false;
187598
188123
  try {
187599
188124
  rmSync2(packageDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
@@ -187615,7 +188140,7 @@ function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackage
187615
188140
  if (nodeModulesDir && basename2(nodeModulesDir) === "node_modules") {
187616
188141
  const installDir = dirname6(nodeModulesDir);
187617
188142
  const packageJsonPath = join9(installDir, "package.json");
187618
- if (!existsSync13(packageJsonPath)) {
188143
+ if (!existsSync14(packageJsonPath)) {
187619
188144
  try {
187620
188145
  writeFileSync4(packageJsonPath, `${JSON.stringify({ private: true, dependencies: {} }, null, 2)}
187621
188146
  `);
@@ -187630,7 +188155,7 @@ function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackage
187630
188155
  return null;
187631
188156
  }
187632
188157
  const legacyPackageJsonPath = join9(dirname6(CACHE_DIR), "package.json");
187633
- if (existsSync13(legacyPackageJsonPath)) {
188158
+ if (existsSync14(legacyPackageJsonPath)) {
187634
188159
  return { installDir: dirname6(CACHE_DIR), packageJsonPath: legacyPackageJsonPath };
187635
188160
  }
187636
188161
  return null;
@@ -187755,7 +188280,7 @@ function claimCheckSlot(storageDir, intervalMs) {
187755
188280
  return true;
187756
188281
  try {
187757
188282
  const file2 = join10(storageDir, TIMESTAMP_FILENAME);
187758
- if (existsSync14(file2)) {
188283
+ if (existsSync15(file2)) {
187759
188284
  try {
187760
188285
  const raw = JSON.parse(readFileSync9(file2, "utf-8"));
187761
188286
  const last = typeof raw.lastCheckedMs === "number" ? raw.lastCheckedMs : 0;
@@ -188483,7 +189008,7 @@ init_task_config();
188483
189008
 
188484
189009
  // src/features/magic-context/dreamer/task-executor.ts
188485
189010
  import { createHash as createHash10 } from "node:crypto";
188486
- import { existsSync as existsSync19 } from "node:fs";
189011
+ import { existsSync as existsSync20 } from "node:fs";
188487
189012
 
188488
189013
  // src/agents/dreamer.ts
188489
189014
  var DREAMER_AGENT = "dreamer";
@@ -189385,6 +189910,9 @@ function requestValidatedAddress(validation, candidate, options) {
189385
189910
  }
189386
189911
  chunks.push(buf);
189387
189912
  });
189913
+ response.on("error", (error51) => {
189914
+ reject(toNetworkError(error51, "response failed"));
189915
+ });
189388
189916
  response.on("end", () => {
189389
189917
  const status = response.statusCode ?? 0;
189390
189918
  if (status >= 500) {
@@ -190093,14 +190621,21 @@ function toSmartNote(note) {
190093
190621
  };
190094
190622
  }
190095
190623
  function commitSmartNoteState(db, args) {
190624
+ db.exec("BEGIN IMMEDIATE");
190096
190625
  let leaseLost = false;
190097
- db.transaction(() => {
190626
+ try {
190098
190627
  if (args.leaseHeld && !args.leaseHeld()) {
190099
190628
  leaseLost = true;
190100
- return;
190629
+ } else {
190630
+ args.write();
190101
190631
  }
190102
- args.write();
190103
- })();
190632
+ db.exec("COMMIT");
190633
+ } catch (error51) {
190634
+ try {
190635
+ db.exec("ROLLBACK");
190636
+ } catch {}
190637
+ throw error51;
190638
+ }
190104
190639
  if (leaseLost) {
190105
190640
  throw new Error(`Dream lease lost during smart-note ${args.phase} commit`);
190106
190641
  }
@@ -190570,7 +191105,7 @@ Output exactly JSON: {"met": false}`;
190570
191105
 
190571
191106
  // src/features/magic-context/dreamer/maintain-docs-protected-enforcement.ts
190572
191107
  init_logger();
190573
- import { existsSync as existsSync17, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
191108
+ import { existsSync as existsSync18, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
190574
191109
  import { join as join15 } from "node:path";
190575
191110
 
190576
191111
  // src/features/magic-context/dreamer/protected-regions.ts
@@ -190674,7 +191209,7 @@ function snapshotMaintainDocsFiles(docsDir) {
190674
191209
  for (const name2 of MAINTAIN_DOCS_SNAPSHOT_FILES) {
190675
191210
  const path8 = join15(docsDir, name2);
190676
191211
  try {
190677
- if (existsSync17(path8)) {
191212
+ if (existsSync18(path8)) {
190678
191213
  snapshot.set(name2, readFileSync11(path8, "utf8"));
190679
191214
  }
190680
191215
  } catch {}
@@ -190706,7 +191241,7 @@ init_memory();
190706
191241
  init_subagent_token_capture();
190707
191242
 
190708
191243
  // src/features/magic-context/dreamer/map-memories-prompt.ts
190709
- import { existsSync as existsSync18, statSync as statSync6 } from "node:fs";
191244
+ import { existsSync as existsSync19, statSync as statSync6 } from "node:fs";
190710
191245
  import path8 from "node:path";
190711
191246
  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.
190712
191247
 
@@ -190744,7 +191279,7 @@ function extractMemoryCandidatePaths(content, repoDir) {
190744
191279
  if (!abs.startsWith(`${root}/`))
190745
191280
  continue;
190746
191281
  try {
190747
- if (existsSync18(abs) && statSync6(abs).isFile())
191282
+ if (existsSync19(abs) && statSync6(abs).isFile())
190748
191283
  found.add(rel);
190749
191284
  } catch {}
190750
191285
  if (found.size >= MAX_SEED_PATHS_PER_MEMORY)
@@ -192573,8 +193108,8 @@ async function runAgenticTask(config2, ctx, helpers) {
192573
193108
  const lastRunAt = getTaskScheduleState(db, projectIdentity, config2.task)?.lastRunAt ?? null;
192574
193109
  const maintainDocsSnapshot = task === "maintain-docs" ? snapshotMaintainDocsFiles(docsDir) : undefined;
192575
193110
  const existingDocs = task === "maintain-docs" ? {
192576
- architecture: existsSync19(`${docsDir}/ARCHITECTURE.md`),
192577
- structure: existsSync19(`${docsDir}/STRUCTURE.md`)
193111
+ architecture: existsSync20(`${docsDir}/ARCHITECTURE.md`),
193112
+ structure: existsSync20(`${docsDir}/STRUCTURE.md`)
192578
193113
  } : undefined;
192579
193114
  const userMemories = task === "curate" ? getActiveUserMemories(db).map((um) => ({ id: um.id, content: um.content })) : undefined;
192580
193115
  let curateMemories;
@@ -194741,6 +195276,17 @@ ${c.content}
194741
195276
  init_send_session_notification();
194742
195277
  var recompConfirmationBySession = new Map;
194743
195278
  var RECOMP_CONFIRMATION_WINDOW_MS = 60000;
195279
+ function isSubagentSession(db, sessionId) {
195280
+ const meta3 = getOrCreateSessionMeta(db, sessionId);
195281
+ if (meta3.isSubagent)
195282
+ return true;
195283
+ try {
195284
+ const row = db.prepare("SELECT is_subagent FROM session_meta WHERE session_id = ?").get(sessionId);
195285
+ return row?.is_subagent === 1 || row?.is_subagent === true;
195286
+ } catch {
195287
+ return false;
195288
+ }
195289
+ }
194744
195290
  var RECOMP_USAGE = [
194745
195291
  "Usage:",
194746
195292
  "- `/ctx-recomp` — full rebuild from message 1 to the protected tail",
@@ -194779,6 +195325,22 @@ ${RECOMP_USAGE}`
194779
195325
  }
194780
195326
  return { kind: "partial", range: { start, end } };
194781
195327
  }
195328
+ function parseWrapupArgs(raw) {
195329
+ const trimmed = raw.trim();
195330
+ if (trimmed === "")
195331
+ return { ok: true, messagesToKeep: 20 };
195332
+ if (!/^\d+$/.test(trimmed)) {
195333
+ return {
195334
+ ok: false,
195335
+ message: "Usage: `/ctx-wrapup [messages_to_keep]` where messages_to_keep is a positive integer."
195336
+ };
195337
+ }
195338
+ const messagesToKeep = Number.parseInt(trimmed, 10);
195339
+ if (!Number.isSafeInteger(messagesToKeep) || messagesToKeep <= 0) {
195340
+ return { ok: false, message: "messages_to_keep must be a positive integer." };
195341
+ }
195342
+ return { ok: true, messagesToKeep };
195343
+ }
194782
195344
  var SENTINEL_PREFIX = "__CONTEXT_MANAGEMENT_";
194783
195345
  var HTTP_SERVER_RESPONSE_TYPE_ID = "~effect/http/HttpServerResponse";
194784
195346
  var HTTP_COOKIES_TYPE_ID = "~effect/http/Cookies";
@@ -194920,6 +195482,7 @@ function createMagicContextCommandHandler(deps) {
194920
195482
  const isStatusCommand = (command) => command === "ctx-status";
194921
195483
  const isFlushCommand = (command) => command === "ctx-flush";
194922
195484
  const isRecompCommand = (command) => command === "ctx-recomp";
195485
+ const isWrapupCommand = (command) => command === "ctx-wrapup";
194923
195486
  const isAugCommand = (command) => command === "ctx-aug";
194924
195487
  const isDreamCommand = (command) => command === "ctx-dream";
194925
195488
  const isSessionUpgradeCommand = (command) => command === "ctx-session-upgrade";
@@ -194929,11 +195492,12 @@ function createMagicContextCommandHandler(deps) {
194929
195492
  const isStatus = isStatusCommand(input.command);
194930
195493
  const isFlush = isFlushCommand(input.command);
194931
195494
  const isRecomp = isRecompCommand(input.command);
195495
+ const isWrapup = isWrapupCommand(input.command);
194932
195496
  const isAug = isAugCommand(input.command);
194933
195497
  const isDream = isDreamCommand(input.command);
194934
195498
  const isSessionUpgrade = isSessionUpgradeCommand(input.command);
194935
195499
  const isEmbed = isEmbedCommand(input.command);
194936
- if (!isStatus && !isFlush && !isRecomp && !isAug && !isDream && !isSessionUpgrade && !isEmbed) {
195500
+ if (!isStatus && !isFlush && !isRecomp && !isWrapup && !isAug && !isDream && !isSessionUpgrade && !isEmbed) {
194937
195501
  return;
194938
195502
  }
194939
195503
  const sessionId = input.sessionID;
@@ -195003,6 +195567,26 @@ Embedding status is unavailable.`;
195003
195567
 
195004
195568
  ${statusOutput}` : statusOutput;
195005
195569
  }
195570
+ if (isWrapup) {
195571
+ const parsed = parseWrapupArgs(input.arguments);
195572
+ if (isSubagentSession(deps.db, sessionId)) {
195573
+ result = `## Magic Wrapup — Skipped
195574
+
195575
+ /ctx-wrapup is only available in primary sessions.`;
195576
+ } else if (!parsed.ok) {
195577
+ result = `## Magic Wrapup — Invalid Arguments
195578
+
195579
+ ${parsed.message}`;
195580
+ } else if (!deps.executeWrapup) {
195581
+ result = `## Magic Wrapup
195582
+
195583
+ /ctx-wrapup is unavailable because the historian handler is not configured.`;
195584
+ } else {
195585
+ result = await deps.executeWrapup(sessionId, {
195586
+ messagesToKeep: parsed.messagesToKeep
195587
+ });
195588
+ }
195589
+ }
195006
195590
  if (isRecomp) {
195007
195591
  const parsedArgs = parseRecompArgs(input.arguments);
195008
195592
  if (parsedArgs.kind === "error") {
@@ -195309,7 +195893,8 @@ var canonicalReasons = new Set([
195309
195893
  "first_render",
195310
195894
  "pressure_refold",
195311
195895
  "upgrade_state",
195312
- "cached_m1_missing"
195896
+ "cached_m1_missing",
195897
+ "project_change"
195313
195898
  ]);
195314
195899
  var piReasonAliases = {
195315
195900
  project_memory_change: "project_memory_epoch",
@@ -196240,35 +196825,31 @@ function resolveCtxReduceAvailabilityFromMessages(sessionId, messages) {
196240
196825
  for (const message of messages) {
196241
196826
  if (message.info?.role !== "user")
196242
196827
  continue;
196243
- const verdict = verdictFromToolsMap(message.info.tools);
196244
- if (verdict !== null) {
196245
- availabilityBySession.set(sessionId, verdict);
196246
- return verdict;
196247
- }
196248
- break;
196828
+ const verdict = verdictFromToolsMap(message.info.tools) ?? true;
196829
+ availabilityBySession.set(sessionId, verdict);
196830
+ return verdict;
196249
196831
  }
196250
- availabilityBySession.set(sessionId, true);
196251
196832
  return true;
196252
196833
  }
196253
196834
  function resolveCtxReduceAvailability(sessionId) {
196254
196835
  const cached2 = availabilityBySession.get(sessionId);
196255
196836
  if (cached2 !== undefined)
196256
- return cached2;
196837
+ return { callable: cached2, frozen: true };
196257
196838
  if (!openCodeDbExists())
196258
- return true;
196839
+ return { callable: true, frozen: true };
196259
196840
  try {
196260
196841
  const row = withReadOnlySessionDb((db) => db.prepare(`SELECT json_extract(data, '$.tools') AS tools FROM message
196261
196842
  WHERE session_id = ? AND json_extract(data, '$.role') = 'user'
196262
196843
  ORDER BY time_created ASC LIMIT 1`).get(sessionId));
196263
196844
  if (!row)
196264
- return true;
196845
+ return { callable: true, frozen: false };
196265
196846
  const verdict = row.tools === null ? null : verdictFromToolsMap(JSON.parse(row.tools));
196266
196847
  const resolved = verdict ?? true;
196267
196848
  availabilityBySession.set(sessionId, resolved);
196268
- return resolved;
196849
+ return { callable: resolved, frozen: true };
196269
196850
  } catch (error51) {
196270
196851
  sessionLog(sessionId, "ctx_reduce availability read failed (fail-open):", error51);
196271
- return true;
196852
+ return { callable: true, frozen: false };
196272
196853
  }
196273
196854
  }
196274
196855
 
@@ -197957,6 +198538,7 @@ function containsProbeVerbatim(text, probes) {
197957
198538
  init_memory();
197958
198539
  init_embedding();
197959
198540
  init_storage_memory_fts();
198541
+ init_storage_notes();
197960
198542
  init_storage_primers();
197961
198543
  init_workspaces();
197962
198544
  var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
@@ -198360,6 +198942,147 @@ function searchMessages(args) {
198360
198942
  role: entry.row.role
198361
198943
  }));
198362
198944
  }
198945
+ var NOTE_SEARCHABLE_STATUSES = ["active", "pending", "ready", "dismissed"];
198946
+ function noteSearchText(note) {
198947
+ const reason = note.readyReason?.trim();
198948
+ return reason ? `${note.content}
198949
+ Reason: ${reason}` : note.content;
198950
+ }
198951
+ function tokenizeKeywordNeedle(text) {
198952
+ const matches = text.toLowerCase().match(/[a-z0-9/._:-]+/g) ?? [];
198953
+ const seen = new Set;
198954
+ const tokens = [];
198955
+ for (const match of matches) {
198956
+ if (match.length <= 1 || !/[a-z0-9]/.test(match) || seen.has(match)) {
198957
+ continue;
198958
+ }
198959
+ seen.add(match);
198960
+ tokens.push(match);
198961
+ }
198962
+ return tokens;
198963
+ }
198964
+ function rankNotesForNeedle(notes, needle) {
198965
+ const normalizedNeedle = needle.trim().toLowerCase();
198966
+ if (normalizedNeedle.length === 0) {
198967
+ return [];
198968
+ }
198969
+ const needleTokens = tokenizeKeywordNeedle(normalizedNeedle);
198970
+ const ranked = [];
198971
+ for (const note of notes) {
198972
+ const text = noteSearchText(note);
198973
+ const normalizedText = text.toLowerCase();
198974
+ const noteTokens = new Set(tokenizeKeywordNeedle(normalizedText));
198975
+ const exact = normalizedText.includes(normalizedNeedle);
198976
+ const matchedTokens = needleTokens.filter((token) => noteTokens.has(token)).length;
198977
+ if (!exact && matchedTokens === 0) {
198978
+ continue;
198979
+ }
198980
+ const coverage = needleTokens.length > 0 ? matchedTokens / needleTokens.length : 0;
198981
+ const score = (exact ? 2 : 0) + coverage + (needleTokens.length > 1 && matchedTokens === needleTokens.length ? 0.5 : 0);
198982
+ ranked.push({ note, score, text });
198983
+ }
198984
+ return ranked.sort((left, right) => {
198985
+ if (right.score !== left.score) {
198986
+ return right.score - left.score;
198987
+ }
198988
+ if (right.note.createdAt !== left.note.createdAt) {
198989
+ return right.note.createdAt - left.note.createdAt;
198990
+ }
198991
+ return left.note.id - right.note.id;
198992
+ });
198993
+ }
198994
+ function searchNotes(args) {
198995
+ if (args.limit <= 0) {
198996
+ return [];
198997
+ }
198998
+ const notes = [
198999
+ ...getNotes(args.db, {
199000
+ sessionId: args.sessionId,
199001
+ type: "session",
199002
+ status: NOTE_SEARCHABLE_STATUSES
199003
+ }),
199004
+ ...getNotes(args.db, {
199005
+ projectPath: args.projectPath,
199006
+ type: "smart",
199007
+ status: NOTE_SEARCHABLE_STATUSES
199008
+ })
199009
+ ];
199010
+ if (notes.length === 0) {
199011
+ return [];
199012
+ }
199013
+ const baseList = rankNotesForNeedle(notes, args.query);
199014
+ const probes = args.probes ?? [];
199015
+ if (probes.length === 0) {
199016
+ const ranked2 = baseList.slice(0, args.limit);
199017
+ return ranked2.map((entry, rank) => ({
199018
+ source: "note",
199019
+ content: previewText(entry.text),
199020
+ score: linearDecayScore(rank, ranked2.length),
199021
+ noteId: entry.note.id,
199022
+ status: entry.note.status,
199023
+ createdAt: entry.note.createdAt,
199024
+ anchorOrdinal: entry.note.anchorOrdinal,
199025
+ sourceSessionId: entry.note.sessionId
199026
+ }));
199027
+ }
199028
+ const queryLists = [];
199029
+ if (baseList.length > 0) {
199030
+ queryLists.push({ rows: baseList, weight: 1 });
199031
+ }
199032
+ const probeWeights = new Map;
199033
+ for (const probe of probes) {
199034
+ const rows = rankNotesForNeedle(notes, probe);
199035
+ if (rows.length === 0) {
199036
+ continue;
199037
+ }
199038
+ const weight = probeDiscriminationWeight(rows.length, notes.length);
199039
+ probeWeights.set(probe, weight);
199040
+ queryLists.push({ rows, weight });
199041
+ }
199042
+ const fused = new Map;
199043
+ for (const list of queryLists) {
199044
+ list.rows.forEach((row, rank) => {
199045
+ const rrf = list.weight / (RRF_K + rank);
199046
+ const existing = fused.get(row.note.id);
199047
+ if (existing) {
199048
+ existing.score += rrf;
199049
+ } else {
199050
+ fused.set(row.note.id, { entry: row, score: rrf });
199051
+ }
199052
+ });
199053
+ }
199054
+ for (const match of fused.values()) {
199055
+ let best = 0;
199056
+ for (const probe of probes) {
199057
+ const weight = probeWeights.get(probe) ?? 0;
199058
+ if (weight > best && containsProbeVerbatim(match.entry.text, [probe])) {
199059
+ best = weight;
199060
+ }
199061
+ }
199062
+ if (best > 0) {
199063
+ match.score += best * VERBATIM_RANK_BONUS;
199064
+ }
199065
+ }
199066
+ const ranked = [...fused.values()].sort((left, right) => {
199067
+ if (right.score !== left.score) {
199068
+ return right.score - left.score;
199069
+ }
199070
+ if (right.entry.note.createdAt !== left.entry.note.createdAt) {
199071
+ return right.entry.note.createdAt - left.entry.note.createdAt;
199072
+ }
199073
+ return left.entry.note.id - right.entry.note.id;
199074
+ }).slice(0, args.limit);
199075
+ return ranked.map((entry, rank) => ({
199076
+ source: "note",
199077
+ content: previewText(entry.entry.text),
199078
+ score: linearDecayScore(rank, ranked.length),
199079
+ noteId: entry.entry.note.id,
199080
+ status: entry.entry.note.status,
199081
+ createdAt: entry.entry.note.createdAt,
199082
+ anchorOrdinal: entry.entry.note.anchorOrdinal,
199083
+ sourceSessionId: entry.entry.note.sessionId
199084
+ }));
199085
+ }
198363
199086
  function searchCompartmentChunks(args) {
198364
199087
  if (!args.queryEmbedding || args.limit <= 0 || !args.modelId || args.modelId === "off")
198365
199088
  return [];
@@ -198445,6 +199168,8 @@ function getSourceBoost(result) {
198445
199168
  return GIT_COMMIT_SOURCE_BOOST;
198446
199169
  case "primer":
198447
199170
  return PRIMER_SOURCE_BOOST;
199171
+ case "note":
199172
+ return 1;
198448
199173
  }
198449
199174
  }
198450
199175
  function compareUnifiedResults(left, right) {
@@ -198468,6 +199193,9 @@ function compareUnifiedResults(left, right) {
198468
199193
  if (left.source === "primer" && right.source === "primer") {
198469
199194
  return right.support - left.support || left.primerId - right.primerId;
198470
199195
  }
199196
+ if (left.source === "note" && right.source === "note") {
199197
+ return right.createdAt - left.createdAt || left.noteId - right.noteId;
199198
+ }
198471
199199
  return 0;
198472
199200
  }
198473
199201
  function toGitCommitResult(hit) {
@@ -198535,11 +199263,11 @@ function searchPrimers(args) {
198535
199263
  }
198536
199264
  function resolveSources(sources) {
198537
199265
  if (sources === undefined) {
198538
- return new Set(["memory", "message", "git_commit", "primer"]);
199266
+ return new Set(["memory", "message", "git_commit", "primer", "note"]);
198539
199267
  }
198540
199268
  const set2 = new Set;
198541
199269
  for (const source of sources) {
198542
- if (source === "memory" || source === "message" || source === "git_commit" || source === "primer") {
199270
+ if (source === "memory" || source === "message" || source === "git_commit" || source === "primer" || source === "note") {
198543
199271
  set2.add(source);
198544
199272
  }
198545
199273
  }
@@ -198562,6 +199290,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
198562
199290
  const runMessages = activeSources.has("message");
198563
199291
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
198564
199292
  const runPrimers = activeSources.has("primer") && memoryFeatureEnabled;
199293
+ const runNotes = activeSources.has("note");
198565
199294
  const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
198566
199295
  const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks || runPrimers) && embeddingEnabled && isEmbeddingRuntimeEnabled();
198567
199296
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error51) => {
@@ -198597,7 +199326,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
198597
199326
  compartments: compartmentResults,
198598
199327
  limit: tierLimit
198599
199328
  });
198600
- const [memoryResults, gitCommitResults, primerResults] = await Promise.all([
199329
+ const [memoryResults, gitCommitResults, primerResults, noteResults] = await Promise.all([
198601
199330
  runMemory ? searchMemories({
198602
199331
  db,
198603
199332
  projectPath,
@@ -198624,9 +199353,23 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
198624
199353
  limit: tierLimit,
198625
199354
  queryEmbedding,
198626
199355
  queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null
199356
+ })) : Promise.resolve([]),
199357
+ runNotes ? Promise.resolve(searchNotes({
199358
+ db,
199359
+ sessionId,
199360
+ projectPath,
199361
+ query: trimmedQuery,
199362
+ limit: tierLimit,
199363
+ probes: messageProbes
198627
199364
  })) : Promise.resolve([])
198628
199365
  ]);
198629
- const results = [...memoryResults, ...primerResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
199366
+ const results = [
199367
+ ...memoryResults,
199368
+ ...primerResults,
199369
+ ...messageLikeResults,
199370
+ ...gitCommitResults,
199371
+ ...noteResults
199372
+ ].sort(compareUnifiedResults).slice(0, limit);
198630
199373
  const countRetrievals = options.countRetrievals ?? true;
198631
199374
  if (countRetrievals) {
198632
199375
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -198699,6 +199442,10 @@ function renderFragment(result, charCap) {
198699
199442
  const compressed = cavemanCompress(result.content, "ultra");
198700
199443
  return truncate(compressed, charCap);
198701
199444
  }
199445
+ case "note": {
199446
+ const compressed = cavemanCompress(result.content, "ultra");
199447
+ return truncate(compressed, charCap);
199448
+ }
198702
199449
  }
198703
199450
  }
198704
199451
  function buildAutoSearchHint(results, options = {}) {
@@ -199565,6 +200312,13 @@ var degradedCacheCountBySession = new BoundedSessionMap(100);
199565
200312
  function resetDegradedCacheCount(sessionId) {
199566
200313
  degradedCacheCountBySession.delete(sessionId);
199567
200314
  }
200315
+ function pendingMarkerCoveredByConsumedBoundary(pending, injection) {
200316
+ if (!injection)
200317
+ return false;
200318
+ if (pending.endMessageId === injection.compartmentEndMessageId)
200319
+ return true;
200320
+ return pending.ordinal <= injection.compartmentEndMessage;
200321
+ }
199568
200322
  function clearPendingCompactionMarkerAfterSuccessfulDrain(args) {
199569
200323
  if (clearPendingCompactionMarkerStateIf(args.db, args.sessionId, args.pending)) {
199570
200324
  return "cleared";
@@ -199963,24 +200717,29 @@ async function runPostTransformPhase(args) {
199963
200717
  if (historyWasConsumedThisPass && args.deferredHistoryWasPendingAtPassStart) {
199964
200718
  const pending = getPendingCompactionMarkerState(args.db, args.sessionId);
199965
200719
  if (pending) {
199966
- const outcome = applyDeferredCompactionMarker(args.db, args.sessionId, pending, args.sessionDirectory);
199967
- switch (outcome.kind) {
199968
- case "applied":
199969
- case "already-current":
199970
- case "stale-skip":
199971
- if (clearPendingCompactionMarkerAfterSuccessfulDrain({
199972
- db: args.db,
199973
- sessionId: args.sessionId,
199974
- pending,
199975
- deferredHistoryRefreshSessions: args.deferredHistoryRefreshSessions
199976
- }) === "cas-lost-newer-pending") {
200720
+ if (!pendingMarkerCoveredByConsumedBoundary(pending, args.pendingCompartmentInjection)) {
200721
+ suppressV12HistoryDrain = true;
200722
+ sessionLog(args.sessionId, `compaction-marker drain: pending ordinal ${pending.ordinal} is newer than consumed boundary ${args.pendingCompartmentInjection?.compartmentEndMessage ?? "<none>"}; preserving deferred history refresh signal`);
200723
+ } else {
200724
+ const outcome = applyDeferredCompactionMarker(args.db, args.sessionId, pending, args.sessionDirectory);
200725
+ switch (outcome.kind) {
200726
+ case "applied":
200727
+ case "already-current":
200728
+ case "stale-skip":
200729
+ if (clearPendingCompactionMarkerAfterSuccessfulDrain({
200730
+ db: args.db,
200731
+ sessionId: args.sessionId,
200732
+ pending,
200733
+ deferredHistoryRefreshSessions: args.deferredHistoryRefreshSessions
200734
+ }) === "cas-lost-newer-pending") {
200735
+ suppressV12HistoryDrain = true;
200736
+ }
200737
+ break;
200738
+ case "retryable-failure":
200739
+ sessionLog(args.sessionId, "compaction-marker drain: retryable failure; preserving deferred history refresh signal", outcome.error);
199977
200740
  suppressV12HistoryDrain = true;
199978
- }
199979
- break;
199980
- case "retryable-failure":
199981
- sessionLog(args.sessionId, "compaction-marker drain: retryable failure; preserving deferred history refresh signal", outcome.error);
199982
- suppressV12HistoryDrain = true;
199983
- break;
200741
+ break;
200742
+ }
199984
200743
  }
199985
200744
  }
199986
200745
  }
@@ -200085,6 +200844,16 @@ function getMessageTokensCache(sessionId) {
200085
200844
  }
200086
200845
  return cache;
200087
200846
  }
200847
+ function maybeSendProjectIdentityWarning(deps, sessionId, directory, notificationParams) {
200848
+ if (!deps.client)
200849
+ return;
200850
+ const warning = takeDubiousOwnershipProjectIdentityWarning(directory);
200851
+ if (!warning)
200852
+ return;
200853
+ sendIgnoredMessage(deps.client, sessionId, warning, notificationParams).catch((error51) => {
200854
+ sessionLog(sessionId, `project identity warning delivery failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
200855
+ });
200856
+ }
200088
200857
  function clearMessageTokensCache(sessionId, messageId) {
200089
200858
  if (messageId === undefined) {
200090
200859
  messageTokensBySession.delete(sessionId);
@@ -200159,7 +200928,7 @@ function createTransform(deps) {
200159
200928
  }
200160
200929
  const reducedMode = sessionMeta.isSubagent;
200161
200930
  const fullFeatureMode = !reducedMode;
200162
- const ctxReduceEnabledEffective = deps.ctxReduceEnabled !== false && resolveCtxReduceAvailabilityFromMessages(sessionId, messages);
200931
+ const ctxReduceCallable = resolveCtxReduceAvailabilityFromMessages(sessionId, messages);
200163
200932
  let sessionDirectory = deps.directory ?? "";
200164
200933
  let sessionDirectoryResolvedFromHost = false;
200165
200934
  const cachedDirectory = deps.sessionDirectoryBySession?.get(sessionId);
@@ -200442,8 +201211,15 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
200442
201211
  }
200443
201212
  }
200444
201213
  logTransformTiming(sessionId, "emergencyRecoveryBlock", tFirstPass);
200445
- const projectIdentity = deps.memoryConfig?.enabled ? resolveProjectIdentity(compartmentDirectory || process.cwd()) : undefined;
201214
+ const memoryProjectDirectory = compartmentDirectory || process.cwd();
201215
+ const projectIdentity = deps.memoryConfig?.enabled ? resolveProjectIdentity(memoryProjectDirectory) : undefined;
201216
+ if (deps.memoryConfig?.enabled) {
201217
+ maybeSendProjectIdentityWarning(deps, sessionId, memoryProjectDirectory, notificationParams);
201218
+ }
200446
201219
  const sessionProjectIdentity = projectIdentity ?? (sessionDirectory ? resolveProjectIdentity(sessionDirectory) : deps.projectPath);
201220
+ if (sessionDirectory) {
201221
+ maybeSendProjectIdentityWarning(deps, sessionId, sessionDirectory, notificationParams);
201222
+ }
200447
201223
  if (sessionProjectIdentity && sessionDirectoryResolvedFromHost && recordedSessionProjectIdentity.get(sessionId) !== sessionProjectIdentity) {
200448
201224
  recordSessionProjectIdentity(db, sessionId, sessionProjectIdentity);
200449
201225
  recordedSessionProjectIdentity.set(sessionId, sessionProjectIdentity);
@@ -200501,7 +201277,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
200501
201277
  const tInitFromDb = performance.now();
200502
201278
  deps.tagger.initFromDb(sessionId, db, taggerFloor);
200503
201279
  logTransformTiming(sessionId, "tag.initFromDb", tInitFromDb);
200504
- const skipPrefixInjection = !ctxReduceEnabledEffective;
201280
+ const skipPrefixInjection = !ctxReduceCallable;
200505
201281
  const result = tagMessages(sessionId, messages, deps.tagger, db, {
200506
201282
  skipPrefixInjection
200507
201283
  });
@@ -200558,7 +201334,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
200558
201334
  }
200559
201335
  logTransformTiming(sessionId, "replayReasoningClearing", tReplay);
200560
201336
  }
200561
- if (!deps.ctxReduceEnabled && !reducedMode && deps.cavemanTextCompression?.enabled) {
201337
+ if (!reducedMode && deps.cavemanTextCompression?.enabled) {
200562
201338
  const tCavemanReplay = performance.now();
200563
201339
  const replayedCaveman = replayCavemanCompression(sessionId, db, targets, targetsSliceTags);
200564
201340
  if (replayedCaveman > 0) {
@@ -200688,7 +201464,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
200688
201464
  projectPath: sessionProjectIdentity,
200689
201465
  sessionDirectory,
200690
201466
  autoSearch: deps.autoSearch,
200691
- cavemanTextCompression: deps.ctxReduceEnabled === false && !reducedMode ? deps.cavemanTextCompression : undefined,
201467
+ cavemanTextCompression: !reducedMode ? deps.cavemanTextCompression : undefined,
200692
201468
  smartDrops: deps.smartDrops === true,
200693
201469
  resolvedProviderID,
200694
201470
  historyRefreshSessions: deps.historyRefreshSessions,
@@ -200844,7 +201620,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
200844
201620
  sessionLog(sessionId, "conversation_tokens UPDATE failed:", error51);
200845
201621
  }
200846
201622
  }
200847
- if (ctxReduceEnabledEffective && deps.channel1StateBySession) {
201623
+ if (ctxReduceCallable && deps.channel1StateBySession) {
200848
201624
  try {
200849
201625
  const resolvedExecuteThresholdPct = resolveExecuteThreshold(deps.executeThresholdPercentage ?? 65, deps.getModelKey?.(sessionId), 65, {
200850
201626
  tokensConfig: deps.executeThresholdTokens,
@@ -201329,6 +202105,343 @@ function createTextCompleteHandler() {
201329
202105
  };
201330
202106
  }
201331
202107
 
202108
+ // src/hooks/magic-context/wrapup-orchestrator.ts
202109
+ init_compartment_lease();
202110
+ init_compartment_storage();
202111
+ init_logger();
202112
+ init_rpc_notifications();
202113
+ init_send_session_notification();
202114
+ await __promiseAll([
202115
+ init_storage(),
202116
+ init_compartment_runner(),
202117
+ init_compartment_runner_incremental(),
202118
+ init_protected_tail_boundary(),
202119
+ init_recomp_orchestrator()
202120
+ ]);
202121
+ import * as crypto3 from "node:crypto";
202122
+ var WAIT_FOR_LEASE_MS = 1000;
202123
+ function sleep2(ms) {
202124
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
202125
+ }
202126
+ function plural(value, word) {
202127
+ return `${value} ${word}${value === 1 ? "" : "s"}`;
202128
+ }
202129
+ function formatAlreadyRunningMessage(state) {
202130
+ if (!state)
202131
+ return "/ctx-wrapup is already running for this session.";
202132
+ const chunk = state.expectedChunks > 0 ? ` chunk ${state.chunkIndex}/${state.expectedChunks}` : "";
202133
+ const through = state.lastCompartmentEnd > 0 ? ` through message ${state.lastCompartmentEnd}` : "";
202134
+ return `/ctx-wrapup is already running for this session${chunk}${through}. Wait for it to finish, then run /ctx-wrapup again if more history remains.`;
202135
+ }
202136
+ function appendFlushHint(ctx, sessionId, message) {
202137
+ if (ctx.hasPendingNaturalBust?.(sessionId))
202138
+ return message;
202139
+ return `${message} If you want it applied on the very next message, run /ctx-flush first.`;
202140
+ }
202141
+ function buildPlan(ctx, sessionId, messagesToKeep, anchorRawMessageCount) {
202142
+ return resolveWrapupProtectedTailBoundary({
202143
+ db: ctx.db,
202144
+ sessionId,
202145
+ mode: "manual-wrapup",
202146
+ contextLimit: ctx.contextLimit,
202147
+ executeThresholdPercentage: ctx.executeThresholdPercentage,
202148
+ usage: null,
202149
+ usageSource: "manual-none",
202150
+ providerShapeVersion: "opencode-v1",
202151
+ cacheNamespace: `opencode:${sessionId}`,
202152
+ messagesToKeep,
202153
+ anchorRawMessageCount
202154
+ });
202155
+ }
202156
+ function emitWrapupProgress(ctx, sessionId, progress) {
202157
+ const current = ctx.liveSessionState.recompProgressBySession.get(sessionId);
202158
+ ctx.liveSessionState.recompProgressBySession.set(sessionId, {
202159
+ sessionId,
202160
+ kind: "wrapup",
202161
+ phase: "recomp",
202162
+ processedMessages: progress.processedMessages ?? current?.processedMessages ?? 0,
202163
+ totalMessages: progress.totalMessages ?? current?.totalMessages ?? 0,
202164
+ passCount: progress.passCount ?? current?.passCount ?? 0,
202165
+ compartmentsCreated: progress.compartmentsCreated ?? current?.compartmentsCreated ?? 0,
202166
+ startedAt: current?.startedAt ?? Date.now(),
202167
+ updatedAt: Date.now(),
202168
+ note: progress.note
202169
+ });
202170
+ }
202171
+ async function waitForExistingIncrementalRun(sessionId) {
202172
+ const active = getActiveCompartmentRun(sessionId);
202173
+ if (!active)
202174
+ return "ok";
202175
+ if (active.kind === "recomp" || active.kind === "wrapup")
202176
+ return "busy";
202177
+ try {
202178
+ await active.promise;
202179
+ } catch {}
202180
+ return "ok";
202181
+ }
202182
+ async function acquireCompartmentLeaseForWrapup(ctx, sessionId, renewWrapupMarker) {
202183
+ const holderId = crypto3.randomUUID();
202184
+ for (;; ) {
202185
+ const active = getActiveCompartmentRun(sessionId);
202186
+ if (active?.kind === "recomp" || active?.kind === "wrapup")
202187
+ return null;
202188
+ if (active) {
202189
+ emitWrapupProgress(ctx, sessionId, { note: "Waiting for the active historian run…" });
202190
+ try {
202191
+ await active.promise;
202192
+ } catch {}
202193
+ continue;
202194
+ }
202195
+ const lease = acquireCompartmentLease(ctx.db, sessionId, holderId);
202196
+ if (lease)
202197
+ return holderId;
202198
+ emitWrapupProgress(ctx, sessionId, { note: "Waiting for the compartment-state lease…" });
202199
+ if (!renewWrapupMarker({}))
202200
+ return null;
202201
+ await sleep2(WAIT_FOR_LEASE_MS);
202202
+ }
202203
+ }
202204
+ async function runOneWrapupIteration(args) {
202205
+ const { ctx, sessionId, plan, messagesToKeep, anchorRawMessageCount } = args;
202206
+ const leaseHolderId = await acquireCompartmentLeaseForWrapup(ctx, sessionId, args.renewWrapupMarker);
202207
+ if (!leaseHolderId)
202208
+ return false;
202209
+ const renewal = setInterval(() => {
202210
+ try {
202211
+ if (!renewCompartmentLease(ctx.db, sessionId, leaseHolderId)) {
202212
+ sessionLog(sessionId, "wrapup: compartment lease renewal failed");
202213
+ }
202214
+ } catch (err) {
202215
+ sessionLog(sessionId, `wrapup: compartment lease renewal threw; continuing (${err instanceof Error ? err.message : String(err)})`);
202216
+ }
202217
+ }, COMPARTMENT_LEASE_RENEWAL_MS);
202218
+ const runCompartmentAgentForWrapup = ctx.runCompartmentAgentForWrapup ?? runCompartmentAgent;
202219
+ const runnerPromise = runCompartmentAgentForWrapup({
202220
+ client: ctx.client,
202221
+ db: ctx.db,
202222
+ sessionId,
202223
+ historianChunkTokens: ctx.historianChunkTokens,
202224
+ historianTimeoutMs: ctx.historianTimeoutMs,
202225
+ boundarySnapshot: plan.snapshot,
202226
+ currentContextLimit: ctx.contextLimit,
202227
+ directory: ctx.directory,
202228
+ fallbackModels: ctx.fallbackModels,
202229
+ fallbackModelId: ctx.fallbackModelId,
202230
+ language: ctx.language,
202231
+ historianTwoPass: ctx.historianTwoPass,
202232
+ memoryEnabled: ctx.memoryEnabled,
202233
+ autoPromote: ctx.autoPromote,
202234
+ ensureProjectRegistered: ctx.ensureProjectRegistered,
202235
+ getNotificationParams: () => ctx.getNotificationParams(sessionId),
202236
+ preserveInjectionCacheUntilConsumed: true,
202237
+ compartmentLeaseHolderId: leaseHolderId,
202238
+ forceDrainQuota: true,
202239
+ forceKeepLastCompartment: true,
202240
+ refreshBoundarySnapshot: () => buildPlan(ctx, sessionId, messagesToKeep, anchorRawMessageCount).snapshot,
202241
+ onCompartmentStatePublished: (sid) => {
202242
+ markActiveCompartmentRunPublished(sid);
202243
+ ctx.liveSessionState.deferredHistoryRefreshSessions.add(sid);
202244
+ ctx.liveSessionState.deferredMaterializationSessions.add(sid);
202245
+ },
202246
+ onDeferredMarkerPending: (sid) => {
202247
+ ctx.liveSessionState.deferredHistoryRefreshSessions.add(sid);
202248
+ }
202249
+ });
202250
+ registerActiveCompartmentRun(sessionId, runnerPromise, "wrapup");
202251
+ try {
202252
+ await runnerPromise;
202253
+ return true;
202254
+ } finally {
202255
+ clearInterval(renewal);
202256
+ releaseCompartmentLease(ctx.db, sessionId, leaseHolderId);
202257
+ }
202258
+ }
202259
+ async function runManagedWrapup(ctx, sessionId, options) {
202260
+ const messagesToKeep = Math.max(1, Math.floor(options.messagesToKeep));
202261
+ setRecompStarting(ctx.liveSessionState, sessionId, "Estimating wrapup…", "wrapup");
202262
+ const existingWrapup = getWrapupInProgressState(ctx.db, sessionId);
202263
+ if (existingWrapup) {
202264
+ const message2 = formatAlreadyRunningMessage(existingWrapup);
202265
+ setRecompTerminal(ctx.liveSessionState, sessionId, "skipped", message2);
202266
+ return `## Magic Wrapup — Skipped
202267
+
202268
+ ${message2}`;
202269
+ }
202270
+ const initialPlan = buildPlan(ctx, sessionId, messagesToKeep);
202271
+ if (initialPlan.rawMessagesAboveLastCompartment <= messagesToKeep || !hasRunnableCompartmentWindow(initialPlan.snapshot)) {
202272
+ const message2 = `Nothing to wrap up — only ${initialPlan.rawMessagesAboveLastCompartment} messages above the last compartment.`;
202273
+ setRecompTerminal(ctx.liveSessionState, sessionId, "done", message2);
202274
+ return message2;
202275
+ }
202276
+ const expectedChunks = Math.max(1, Math.ceil(initialPlan.snapshot.trueRawEligibleTokens / Math.max(1, ctx.historianChunkTokens)));
202277
+ const wrapupHolderId = crypto3.randomUUID();
202278
+ const acquired = acquireWrapupInProgress(ctx.db, sessionId, {
202279
+ holderId: wrapupHolderId,
202280
+ messagesToKeep,
202281
+ anchorRawMessageCount: initialPlan.anchorRawMessageCount,
202282
+ targetEligibleEndOrdinal: initialPlan.targetEligibleEndOrdinal,
202283
+ lastCompartmentEnd: getLastCompartmentEndMessage(ctx.db, sessionId),
202284
+ chunkIndex: 0,
202285
+ expectedChunks
202286
+ });
202287
+ if (!acquired.ok) {
202288
+ const message2 = formatAlreadyRunningMessage(acquired.state);
202289
+ setRecompTerminal(ctx.liveSessionState, sessionId, "skipped", message2);
202290
+ return `## Magic Wrapup — Skipped
202291
+
202292
+ ${message2}`;
202293
+ }
202294
+ const startLastEnd = getLastCompartmentEndMessage(ctx.db, sessionId);
202295
+ const startCompartmentCount = getCompartments(ctx.db, sessionId).length;
202296
+ let chunkIndex = 0;
202297
+ let lastEnd = startLastEnd;
202298
+ let stoppedForFailure = false;
202299
+ let stoppedReason = "";
202300
+ let ownershipLost = false;
202301
+ const ownershipLostReason = "another process took over this session's wrapup.";
202302
+ const markOwnershipLost = () => {
202303
+ if (ownershipLost)
202304
+ return;
202305
+ ownershipLost = true;
202306
+ sessionLog(sessionId, "wrapup: durable marker ownership lost; aborting loop");
202307
+ };
202308
+ const renewWrapupMarker = (updates) => {
202309
+ const updated = updateWrapupInProgress(ctx.db, sessionId, wrapupHolderId, updates);
202310
+ if (!updated) {
202311
+ markOwnershipLost();
202312
+ return false;
202313
+ }
202314
+ return true;
202315
+ };
202316
+ const markerRenewal = setInterval(() => {
202317
+ try {
202318
+ renewWrapupMarker({
202319
+ lastCompartmentEnd: getLastCompartmentEndMessage(ctx.db, sessionId),
202320
+ chunkIndex
202321
+ });
202322
+ } catch (err) {
202323
+ sessionLog(sessionId, `wrapup: marker renewal threw; continuing (${err instanceof Error ? err.message : String(err)})`);
202324
+ }
202325
+ }, 60000);
202326
+ markerRenewal.unref?.();
202327
+ try {
202328
+ const activeAtStart = await waitForExistingIncrementalRun(sessionId);
202329
+ if (activeAtStart === "busy") {
202330
+ const message2 = "Another Magic Context rebuild is already running for this session.";
202331
+ setRecompTerminal(ctx.liveSessionState, sessionId, "skipped", message2);
202332
+ return `## Magic Wrapup — Skipped
202333
+
202334
+ ${message2}`;
202335
+ }
202336
+ try {
202337
+ sendIgnoredMessage(ctx.client, sessionId, `Magic Wrapup started — compacting about ${plural(expectedChunks, "chunk")} of history. This can take a few minutes; the result posts here when done.`, ctx.getNotificationParams(sessionId));
202338
+ } catch {}
202339
+ try {
202340
+ pushNotification("action", { action: "wrapup-progress-kick" }, sessionId);
202341
+ } catch {}
202342
+ if (ownershipLost) {
202343
+ stoppedForFailure = true;
202344
+ stoppedReason = ownershipLostReason;
202345
+ } else {
202346
+ emitWrapupProgress(ctx, sessionId, {
202347
+ processedMessages: Math.max(0, lastEnd),
202348
+ totalMessages: Math.max(0, initialPlan.targetEligibleEndOrdinal - 1),
202349
+ passCount: 0,
202350
+ compartmentsCreated: 0,
202351
+ note: `Eligible ${plural(initialPlan.snapshot.trueRawEligibleTokens, "token")} across about ${plural(expectedChunks, "chunk")}.`
202352
+ });
202353
+ for (;; ) {
202354
+ if (ownershipLost) {
202355
+ stoppedForFailure = true;
202356
+ stoppedReason = ownershipLostReason;
202357
+ break;
202358
+ }
202359
+ if (!renewWrapupMarker({
202360
+ lastCompartmentEnd: getLastCompartmentEndMessage(ctx.db, sessionId),
202361
+ chunkIndex,
202362
+ expectedChunks
202363
+ })) {
202364
+ stoppedForFailure = true;
202365
+ stoppedReason = ownershipLostReason;
202366
+ break;
202367
+ }
202368
+ const plan = buildPlan(ctx, sessionId, messagesToKeep, initialPlan.anchorRawMessageCount);
202369
+ lastEnd = getLastCompartmentEndMessage(ctx.db, sessionId);
202370
+ if (lastEnd + 1 >= plan.targetEligibleEndOrdinal)
202371
+ break;
202372
+ chunkIndex += 1;
202373
+ emitWrapupProgress(ctx, sessionId, {
202374
+ processedMessages: Math.max(0, lastEnd),
202375
+ totalMessages: Math.max(0, plan.targetEligibleEndOrdinal - 1),
202376
+ passCount: chunkIndex - 1,
202377
+ compartmentsCreated: Math.max(0, getCompartments(ctx.db, sessionId).length - startCompartmentCount),
202378
+ note: `Chunk ${chunkIndex}/${expectedChunks}: messages ${plan.snapshot.offset}-${plan.snapshot.eligibleEndOrdinal - 1}…`
202379
+ });
202380
+ if (!renewWrapupMarker({
202381
+ lastCompartmentEnd: lastEnd,
202382
+ chunkIndex,
202383
+ expectedChunks,
202384
+ targetEligibleEndOrdinal: plan.targetEligibleEndOrdinal
202385
+ })) {
202386
+ stoppedForFailure = true;
202387
+ stoppedReason = ownershipLostReason;
202388
+ break;
202389
+ }
202390
+ const beforeEnd = lastEnd;
202391
+ const beforeFailures = getHistorianFailureState(ctx.db, sessionId).failureCount;
202392
+ const ran = await runOneWrapupIteration({
202393
+ ctx,
202394
+ sessionId,
202395
+ plan,
202396
+ messagesToKeep,
202397
+ anchorRawMessageCount: initialPlan.anchorRawMessageCount,
202398
+ renewWrapupMarker
202399
+ });
202400
+ if (!ran) {
202401
+ stoppedForFailure = true;
202402
+ stoppedReason = ownershipLost ? ownershipLostReason : "Another Magic Context rebuild started while wrapup was waiting.";
202403
+ break;
202404
+ }
202405
+ const afterEnd = getLastCompartmentEndMessage(ctx.db, sessionId);
202406
+ const afterFailures = getHistorianFailureState(ctx.db, sessionId).failureCount;
202407
+ if (afterEnd <= beforeEnd) {
202408
+ stoppedForFailure = true;
202409
+ stoppedReason = afterFailures > beforeFailures ? "The historian failed on the current chunk." : "The historian made no forward progress on the current chunk.";
202410
+ break;
202411
+ }
202412
+ lastEnd = afterEnd;
202413
+ emitWrapupProgress(ctx, sessionId, {
202414
+ processedMessages: Math.max(0, lastEnd),
202415
+ totalMessages: Math.max(0, plan.targetEligibleEndOrdinal - 1),
202416
+ passCount: chunkIndex,
202417
+ compartmentsCreated: Math.max(0, getCompartments(ctx.db, sessionId).length - startCompartmentCount),
202418
+ note: `Wrapped through message ${lastEnd}.`
202419
+ });
202420
+ }
202421
+ }
202422
+ } finally {
202423
+ clearInterval(markerRenewal);
202424
+ releaseWrapupInProgress(ctx.db, sessionId, wrapupHolderId);
202425
+ }
202426
+ const finalEnd = getLastCompartmentEndMessage(ctx.db, sessionId);
202427
+ const compartmentsCreated = Math.max(0, getCompartments(ctx.db, sessionId).length - startCompartmentCount);
202428
+ const messagesWrapped = Math.max(0, finalEnd - Math.max(0, startLastEnd));
202429
+ if (stoppedForFailure) {
202430
+ const message2 = `Wrapped up through message ${Math.max(0, finalEnd)} (${plural(messagesWrapped, "message")} into ${plural(compartmentsCreated, "compartment")}). ${stoppedReason} Run /ctx-wrapup again to continue.`;
202431
+ setRecompTerminal(ctx.liveSessionState, sessionId, "failed", message2);
202432
+ return `## Magic Wrapup — Partial
202433
+
202434
+ ${message2}`;
202435
+ }
202436
+ try {
202437
+ clearEmergencyRecovery(ctx.db, sessionId);
202438
+ } catch {}
202439
+ const base = `Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments. The compacted history is queued and materializes on your next message.`;
202440
+ const message = appendFlushHint(ctx, sessionId, base);
202441
+ setRecompTerminal(ctx.liveSessionState, sessionId, "done", message);
202442
+ return message;
202443
+ }
202444
+
201332
202445
  // src/hooks/magic-context/hook.ts
201333
202446
  await init_compaction_marker_manager();
201334
202447
 
@@ -201667,7 +202780,7 @@ Drop silently — do not narrate it. NEVER drop large ranges blindly (e.g., "1-5
201667
202780
  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.`;
201668
202781
  var CAVEMAN_COMPRESSION_WARNING = `
201669
202782
  **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.`;
201670
- function buildMagicContextSection(_agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language, memoryEnabled = true) {
202783
+ function buildMagicContextSection(_agent, protectedTags, ctxReduceCallable = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language, memoryEnabled = true) {
201671
202784
  if (subagentMode) {
201672
202785
  return `## Magic Context
201673
202786
 
@@ -201678,12 +202791,12 @@ When \`surface_condition\` is provided with \`write\`, the note becomes a projec
201678
202791
  The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
201679
202792
  Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`` : "";
201680
202793
  const temporalGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : "";
201681
- const cavemanWarning = cavemanTextCompressionEnabled && !ctxReduceEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
202794
+ const cavemanWarning = cavemanTextCompressionEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
201682
202795
  const languageDirective = buildPrimaryLanguageDirective(language);
201683
202796
  const languageGuidance = languageDirective ? `
201684
202797
 
201685
202798
  ${languageDirective}` : "";
201686
- if (!ctxReduceEnabled) {
202799
+ if (!ctxReduceCallable) {
201687
202800
  return `## Magic Context
201688
202801
 
201689
202802
  ${LONG_TERM_PARTNER_FRAME}
@@ -201696,7 +202809,7 @@ ${BASE_INTRO_NO_REDUCE(memoryEnabled)}${smartNoteGuidance}${temporalGuidance}${c
201696
202809
  ${LONG_TERM_PARTNER_FRAME}
201697
202810
  ${PARTNER_FRAME_CLOSER_REDUCE}
201698
202811
 
201699
- ${BASE_INTRO(protectedTags, memoryEnabled)}${smartNoteGuidance}${temporalGuidance}
202812
+ ${BASE_INTRO(protectedTags, memoryEnabled)}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}
201700
202813
  ${GENERIC_SECTION}
201701
202814
 
201702
202815
  Prefer many small targeted operations over one large blanket operation, and keep the working set tidy as routine maintenance.${languageGuidance}`;
@@ -201749,17 +202862,18 @@ function createSystemPromptHashHandler(deps) {
201749
202862
  } catch (error51) {
201750
202863
  sessionLog(sessionId, "system-prompt-hash session meta load failed:", error51);
201751
202864
  }
201752
- const isSubagentSession = sessionMetaEarly?.isSubagent === true;
201753
- const ctxReduceCallable = resolveCtxReduceAvailability(sessionId);
201754
- const subagentReduceMode = isSubagentSession && deps.ctxReduceEnabled !== false && ctxReduceCallable;
201755
- const effectiveCtxReduceEnabled = isSubagentSession ? false : deps.ctxReduceEnabled !== false && ctxReduceCallable;
201756
- const skipGuidanceForDisabledSubagent = isSubagentSession && (deps.ctxReduceEnabled === false || !ctxReduceCallable);
202865
+ const isSubagentSession2 = sessionMetaEarly?.isSubagent === true;
202866
+ const availability = resolveCtxReduceAvailability(sessionId);
202867
+ const ctxReduceCallable = availability.callable;
202868
+ const subagentReduceMode = isSubagentSession2 && ctxReduceCallable;
202869
+ const effectiveCtxReduceEnabled = isSubagentSession2 ? false : ctxReduceCallable;
202870
+ const skipGuidanceForDisabledSubagent = isSubagentSession2 && !ctxReduceCallable;
201757
202871
  const fullPrompt = output.system.join(`
201758
202872
  `);
201759
202873
  if (fullPrompt.length > 0 && !fullPrompt.includes(MAGIC_CONTEXT_MARKER) && !skipGuidanceForDisabledSubagent) {
201760
202874
  const guidance = buildMagicContextSection(null, deps.protectedTags, effectiveCtxReduceEnabled, deps.dreamerEnabled, deps.experimentalTemporalAwareness, deps.experimentalCavemanTextCompression, subagentReduceMode, deps.language, deps.memoryEnabled !== false);
201761
202875
  output.system.push(guidance);
201762
- sessionLog(sessionId, `injected generic guidance into system prompt (ctxReduce=${effectiveCtxReduceEnabled}, subagent=${isSubagentSession}, subagentReduceMode=${subagentReduceMode})`);
202876
+ sessionLog(sessionId, `injected generic guidance into system prompt (ctxReduce=${effectiveCtxReduceEnabled}, subagent=${isSubagentSession2}, subagentReduceMode=${subagentReduceMode})`);
201763
202877
  }
201764
202878
  const isCacheBusting = deps.systemPromptRefreshSessions.has(sessionId);
201765
202879
  const DATE_PATTERN = /Today's date: .+/;
@@ -201786,6 +202900,8 @@ function createSystemPromptHashHandler(deps) {
201786
202900
  `);
201787
202901
  if (systemContent.length === 0)
201788
202902
  return;
202903
+ if (!availability.frozen)
202904
+ return;
201789
202905
  const currentHash = createHash13("md5").update(systemContent).digest("hex");
201790
202906
  if (!sessionMetaEarly) {
201791
202907
  return;
@@ -201974,7 +203090,7 @@ function createMagicContextHook(deps) {
201974
203090
  notifyMagicContextDisabled(deps.client, reason);
201975
203091
  return null;
201976
203092
  }
201977
- const projectPath = resolveProjectIdentity(deps.directory);
203093
+ const projectPath = resolveProjectIdentityOrFallback(deps.directory);
201978
203094
  try {
201979
203095
  checkCompactionMarkerConsistency(db);
201980
203096
  } catch (error51) {
@@ -201985,20 +203101,21 @@ function createMagicContextHook(deps) {
201985
203101
  const historianFallbackModels = resolveFallbackChain(deps.config.historian?.fallback_models);
201986
203102
  const historyRefreshSessions = deps.liveSessionState?.historyRefreshSessions ?? new Set;
201987
203103
  const deferredHistoryRefreshSessions = deps.liveSessionState?.deferredHistoryRefreshSessions ?? new Set;
203104
+ const systemPromptRefreshSessions = deps.liveSessionState?.systemPromptRefreshSessions ?? new Set;
203105
+ const pendingMaterializationSessions = deps.liveSessionState?.pendingMaterializationSessions ?? new Set;
203106
+ const deferredMaterializationSessions = deps.liveSessionState?.deferredMaterializationSessions ?? new Set;
201988
203107
  try {
201989
203108
  const sessionsWithPending = getSessionsWithPendingMarker(db);
201990
203109
  if (sessionsWithPending.length > 0) {
201991
203110
  for (const sid of sessionsWithPending) {
201992
203111
  deferredHistoryRefreshSessions.add(sid);
203112
+ deferredMaterializationSessions.add(sid);
201993
203113
  }
201994
203114
  log(`[magic-context] rehydrated ${sessionsWithPending.length} session(s) with pending compaction-marker drain at hook init`);
201995
203115
  }
201996
203116
  } catch (error51) {
201997
203117
  log("[magic-context] hook init: pending-marker rehydration failed:", error51);
201998
203118
  }
201999
- const systemPromptRefreshSessions = deps.liveSessionState?.systemPromptRefreshSessions ?? new Set;
202000
- const pendingMaterializationSessions = deps.liveSessionState?.pendingMaterializationSessions ?? new Set;
202001
- const deferredMaterializationSessions = deps.liveSessionState?.deferredMaterializationSessions ?? new Set;
202002
203119
  const lastHeuristicsTurnId = new Map;
202003
203120
  const commitSeenLastPass = new Map;
202004
203121
  const variantBySession = deps.liveSessionState?.variantBySession ?? new Map;
@@ -202019,7 +203136,15 @@ function createMagicContextHook(deps) {
202019
203136
  }
202020
203137
  return;
202021
203138
  };
202022
- const ctxReduceEnabled = deps.config.ctx_reduce_enabled !== false;
203139
+ const maybeSendProjectIdentitySessionWarning = (sessionId, directory) => {
203140
+ const warning = takeDubiousOwnershipProjectIdentityWarning(directory);
203141
+ if (!warning)
203142
+ return;
203143
+ const notificationParams = getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession, deps.config.toast_duration_ms);
203144
+ sendIgnoredMessage(deps.client, sessionId, warning, notificationParams).catch((error51) => {
203145
+ log(`[magic-context] failed to send project identity warning for ${directory}: ${getErrorMessage(error51)}`);
203146
+ });
203147
+ };
202023
203148
  const dreamerRunnable = isDreamerRunnable(deps.config);
202024
203149
  const dreamerConfig = dreamerRunnable ? deps.config.dreamer : undefined;
202025
203150
  const historianRunnable = isHistorianRunnable(deps.config);
@@ -202056,6 +203181,23 @@ function createMagicContextHook(deps) {
202056
203181
  ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory,
202057
203182
  getNotificationParams: (sid) => getLiveNotificationParams(sid, liveModelBySession, variantBySession, agentBySession, deps.config.toast_duration_ms)
202058
203183
  });
203184
+ const buildManagedWrapupCtx = (sessionId) => ({
203185
+ ...buildManagedRecompCtx(sessionId),
203186
+ contextLimit: (() => {
203187
+ const model = resolveLiveModel(sessionId);
203188
+ return model ? resolveContextLimit(model.providerID, model.modelID, { db, sessionID: sessionId }) : 128000;
203189
+ })(),
203190
+ executeThresholdPercentage: (() => {
203191
+ const model = resolveLiveModel(sessionId);
203192
+ const contextLimit = model ? resolveContextLimit(model.providerID, model.modelID, { db, sessionID: sessionId }) : 128000;
203193
+ return resolveExecuteThresholdDetail(deps.config.execute_threshold_percentage ?? 65, model ? `${model.providerID}/${model.modelID}` : undefined, 65, {
203194
+ tokensConfig: deps.config.execute_threshold_tokens,
203195
+ contextLimit,
203196
+ sessionId
203197
+ }).percentage;
203198
+ })(),
203199
+ hasPendingNaturalBust: (sid) => historyRefreshSessions.has(sid) || systemPromptRefreshSessions.has(sid) || pendingMaterializationSessions.has(sid)
203200
+ });
202059
203201
  const executeEmbedHistory = async (sessionId, options) => {
202060
203202
  if (deps.config.memory?.enabled === false) {
202061
203203
  return "Memory is disabled for this project, so there is no semantic embedding to backfill.";
@@ -202066,7 +203208,8 @@ function createMagicContextHook(deps) {
202066
203208
  return "Embedding is already running for this session.";
202067
203209
  }
202068
203210
  await ensureProjectRegisteredFromOpenCodeDirectory(directory, db);
202069
- const sessionProjectIdentity = resolveProjectIdentity(directory);
203211
+ const sessionProjectIdentity = resolveProjectIdentityOrFallback(directory);
203212
+ maybeSendProjectIdentitySessionWarning(sessionId, directory);
202070
203213
  embedPauseBySession.delete(sessionId);
202071
203214
  const prior = embedRunStateBySession.get(sessionId);
202072
203215
  if (prior)
@@ -202131,13 +203274,15 @@ function createMagicContextHook(deps) {
202131
203274
  if (ctrl)
202132
203275
  ctrl.abort();
202133
203276
  const directory = sessionDirectoryBySession.get(sessionId) ?? deps.directory;
202134
- const sessionProjectIdentity = resolveProjectIdentity(directory);
203277
+ const sessionProjectIdentity = resolveProjectIdentityOrFallback(directory);
203278
+ maybeSendProjectIdentitySessionWarning(sessionId, directory);
202135
203279
  const cov = getEmbeddingCoverageStatus(db, sessionProjectIdentity, sessionId);
202136
203280
  return `Paused at ${cov.session.embedded}/${cov.session.total} compartments embedded.`;
202137
203281
  };
202138
203282
  const getEmbedStatusText = (sessionId) => {
202139
203283
  const directory = sessionDirectoryBySession.get(sessionId) ?? deps.directory;
202140
- const sessionProjectIdentity = resolveProjectIdentity(directory);
203284
+ const sessionProjectIdentity = resolveProjectIdentityOrFallback(directory);
203285
+ maybeSendProjectIdentitySessionWarning(sessionId, directory);
202141
203286
  const coverage = getEmbeddingCoverageStatus(db, sessionProjectIdentity, sessionId);
202142
203287
  const progress = recompProgressBySession.get(sessionId);
202143
203288
  const drainUi = getEmbedDrainUiStatus(sessionId, progress);
@@ -202160,7 +203305,8 @@ function createMagicContextHook(deps) {
202160
203305
  try {
202161
203306
  await new Promise((resolve4) => setTimeout(resolve4, 0));
202162
203307
  await ensureProjectRegisteredFromOpenCodeDirectory(directory, db);
202163
- const sessionProjectIdentity = resolveProjectIdentity(directory);
203308
+ const sessionProjectIdentity = resolveProjectIdentityOrFallback(directory);
203309
+ maybeSendProjectIdentitySessionWarning(sessionId, directory);
202164
203310
  const coverage = getEmbeddingCoverageStatus(db, sessionProjectIdentity, sessionId);
202165
203311
  if (!coverage.enabled)
202166
203312
  return;
@@ -202194,7 +203340,6 @@ function createMagicContextHook(deps) {
202194
203340
  db,
202195
203341
  channel1StateBySession,
202196
203342
  protectedTags: deps.config.protected_tags,
202197
- ctxReduceEnabled,
202198
203343
  smartDrops: deps.config.smart_drops === true,
202199
203344
  clearReasoningAge: deps.config.clear_reasoning_age ?? 50,
202200
203345
  commitClusterTrigger: deps.config.commit_cluster_trigger,
@@ -202243,7 +203388,7 @@ function createMagicContextHook(deps) {
202243
203388
  directory: deps.directory,
202244
203389
  ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory
202245
203390
  } : undefined,
202246
- cavemanTextCompression: ctxReduceEnabled === false && deps.config.caveman_text_compression?.enabled === true ? {
203391
+ cavemanTextCompression: deps.config.caveman_text_compression?.enabled === true ? {
202247
203392
  enabled: true,
202248
203393
  minChars: deps.config.caveman_text_compression.min_chars ?? 500
202249
203394
  } : undefined,
@@ -202331,6 +203476,7 @@ function createMagicContextHook(deps) {
202331
203476
  systemPromptRefreshSessions.add(sessionId);
202332
203477
  pendingMaterializationSessions.add(sessionId);
202333
203478
  },
203479
+ executeWrapup: historianRunnable ? async (sessionId, options) => runManagedWrapup(buildManagedWrapupCtx(sessionId), sessionId, options) : undefined,
202334
203480
  executeRecomp: historianRunnable ? async (sessionId, options) => runManagedRecomp(buildManagedRecompCtx(sessionId), sessionId, options) : undefined,
202335
203481
  runUpgrade: historianRunnable ? async (sessionId) => runManagedUpgrade(buildManagedRecompCtx(sessionId), sessionId) : undefined,
202336
203482
  executeEmbedHistory,
@@ -202374,7 +203520,6 @@ function createMagicContextHook(deps) {
202374
203520
  const systemPromptHash = createSystemPromptHashHandler({
202375
203521
  db,
202376
203522
  protectedTags: deps.config.protected_tags,
202377
- ctxReduceEnabled,
202378
203523
  dreamerEnabled: dreamerRunnable,
202379
203524
  memoryEnabled: deps.config.memory?.enabled !== false,
202380
203525
  language: deps.config.language,
@@ -202389,7 +203534,7 @@ function createMagicContextHook(deps) {
202389
203534
  internalChildSessions,
202390
203535
  experimentalUserMemories: userMemoryCollectionEnabled(deps.config.dreamer),
202391
203536
  experimentalTemporalAwareness: deps.config.temporal_awareness === true,
202392
- experimentalCavemanTextCompression: ctxReduceEnabled === false && deps.config.caveman_text_compression?.enabled === true
203537
+ experimentalCavemanTextCompression: deps.config.caveman_text_compression?.enabled === true
202393
203538
  });
202394
203539
  const systemPromptHashHandler = systemPromptHash.handler;
202395
203540
  const eventHook = createEventHook({
@@ -202408,8 +203553,7 @@ function createMagicContextHook(deps) {
202408
203553
  lastHeuristicsTurnId,
202409
203554
  commitSeenLastPass,
202410
203555
  client: deps.client,
202411
- protectedTags: deps.config.protected_tags,
202412
- ctxReduceEnabled
203556
+ protectedTags: deps.config.protected_tags
202413
203557
  });
202414
203558
  return {
202415
203559
  "experimental.chat.messages.transform": transform2,
@@ -202424,7 +203568,6 @@ function createMagicContextHook(deps) {
202424
203568
  systemPromptRefreshSessions,
202425
203569
  pendingMaterializationSessions,
202426
203570
  lastHeuristicsTurnId,
202427
- ctxReduceEnabled,
202428
203571
  upgradeReminder: historianRunnable ? (sessionId) => maybeSendUpgradeReminder({
202429
203572
  client: deps.client,
202430
203573
  db,
@@ -202456,7 +203599,6 @@ function createMagicContextHook(deps) {
202456
203599
  function buildMagicContextHookConfig(pluginConfig) {
202457
203600
  return {
202458
203601
  protected_tags: pluginConfig.protected_tags ?? DEFAULT_PROTECTED_TAGS,
202459
- ctx_reduce_enabled: pluginConfig.ctx_reduce_enabled,
202460
203602
  cache_ttl: pluginConfig.cache_ttl,
202461
203603
  clear_reasoning_age: pluginConfig.clear_reasoning_age,
202462
203604
  toast_duration_ms: pluginConfig.toast_duration_ms,
@@ -202977,7 +204119,6 @@ function buildSidebarSnapshot(db, sessionId, directory, liveSessionState, inject
202977
204119
  contextLimit: 0,
202978
204120
  systemPromptTokens: 0,
202979
204121
  compartmentCount: 0,
202980
- factCount: 0,
202981
204122
  memoryCount: 0,
202982
204123
  memoryBlockCount: 0,
202983
204124
  pendingOpsCount: 0,
@@ -203016,7 +204157,6 @@ function buildSidebarSnapshot(db, sessionId, directory, liveSessionState, inject
203016
204157
  const memoryBlockCount = meta3 ? Number(meta3.memory_block_count ?? 0) : 0;
203017
204158
  const compartmentRow = db.prepare("SELECT COUNT(*) as count FROM compartments WHERE session_id = ?").get(sessionId);
203018
204159
  const compartmentCount = compartmentRow?.count ?? 0;
203019
- const factCount = 0;
203020
204160
  let memoryCount = 0;
203021
204161
  if (projectIdentity) {
203022
204162
  const memRow = db.prepare("SELECT COUNT(*) as count FROM memories WHERE project_path = ? AND status = 'active'").get(projectIdentity);
@@ -203123,7 +204263,6 @@ function buildSidebarSnapshot(db, sessionId, directory, liveSessionState, inject
203123
204263
  contextLimit,
203124
204264
  systemPromptTokens: calibrated.systemTokens,
203125
204265
  compartmentCount,
203126
- factCount,
203127
204266
  memoryCount,
203128
204267
  memoryBlockCount,
203129
204268
  pendingOpsCount,
@@ -204136,8 +205275,8 @@ function createCtxMemoryTool(deps) {
204136
205275
  }
204137
205276
  });
204138
205277
  const idList = targets.map((t) => t.memoryId).join(", ");
204139
- const plural = targets.length > 1 ? "memories" : "memory";
204140
- return args.reason?.trim() ? `Archived ${plural} [ID: ${idList}] (${args.reason.trim()}).` : `Archived ${plural} [ID: ${idList}].`;
205278
+ const plural2 = targets.length > 1 ? "memories" : "memory";
205279
+ return args.reason?.trim() ? `Archived ${plural2} [ID: ${idList}] (${args.reason.trim()}).` : `Archived ${plural2} [ID: ${idList}].`;
204141
205280
  }
204142
205281
  return "Error: Unknown action.";
204143
205282
  }
@@ -204542,10 +205681,12 @@ Sources (omit for a broad search across all):
204542
205681
  - memory: curated cross-session project knowledge — rules, constraints, conventions.
204543
205682
  - 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).
204544
205683
  - git_commit: this repository's commit history.
205684
+ - note: parked decisions, follow-ups, and dismissed notes with their recorded text.
204545
205685
 
204546
205686
  Picking sources:
204547
205687
  - "when did this change / was this working before" → ["git_commit", "message"]
204548
205688
  - "did we discuss this earlier" → ["message"]
205689
+ - "did we decide something about this / leave a follow-up" → ["note"]
204549
205690
  - "what's our convention / rule for X" → ["memory"]`;
204550
205691
  var DEFAULT_CTX_SEARCH_LIMIT = 10;
204551
205692
  // src/tools/ctx-search/tools.ts
@@ -204557,7 +205698,8 @@ var VALID_SOURCES = new Set([
204557
205698
  "memory",
204558
205699
  "message",
204559
205700
  "git_commit",
204560
- "primer"
205701
+ "primer",
205702
+ "note"
204561
205703
  ]);
204562
205704
  function normalizeLimit3(limit) {
204563
205705
  if (typeof limit !== "number" || !Number.isFinite(limit)) {
@@ -204600,7 +205742,8 @@ function formatAge2(committedAtMs) {
204600
205742
  const years = Math.floor(days / 365);
204601
205743
  return years === 1 ? "1y ago" : `${years}y ago`;
204602
205744
  }
204603
- function formatResult(result, index) {
205745
+ var NOTE_EXPAND_HINT = "Use ctx_expand(start=N-10, end=N) around any note @msg anchor above to read the surrounding conversation context.";
205746
+ function formatResult(result, index, currentSessionId) {
204604
205747
  if (result.source === "memory") {
204605
205748
  const source = result.sourceName ? ` source=${result.sourceName}` : "";
204606
205749
  return [
@@ -204621,6 +205764,14 @@ function formatResult(result, index) {
204621
205764
  `[${index}] [primer] score=${result.score.toFixed(2)} id=${result.primerId} support=${result.support} match=${result.matchType}`,
204622
205765
  result.content
204623
205766
  ].join(`
205767
+ `);
205768
+ }
205769
+ if (result.source === "note") {
205770
+ const anchor = result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId ? ` @msg ${result.anchorOrdinal}` : "";
205771
+ return [
205772
+ `[${index}] [note] score=${result.score.toFixed(2)} id=#${result.noteId} status=${result.status} ${formatAge2(result.createdAt)}${anchor}`,
205773
+ result.content
205774
+ ].join(`
204624
205775
  `);
204625
205776
  }
204626
205777
  if (result.source === "compartment") {
@@ -204638,14 +205789,17 @@ function formatResult(result, index) {
204638
205789
  ].join(`
204639
205790
  `);
204640
205791
  }
204641
- function formatSearchResults(query, results) {
205792
+ function formatSearchResults(query, results, currentSessionId) {
204642
205793
  if (results.length === 0) {
204643
- return `No results found for "${query}" across memories, primers, git commits, or message history.`;
205794
+ return `No results found for "${query}" across notes, memories, primers, git commits, or message history.`;
204644
205795
  }
204645
- const bodyParts = results.map((result, index) => formatResult(result, index + 1));
205796
+ const bodyParts = results.map((result, index) => formatResult(result, index + 1, currentSessionId));
204646
205797
  if (results.some((result) => result.source === "message" || result.source === "compartment")) {
204647
205798
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
204648
205799
  }
205800
+ if (results.some((result) => result.source === "note" && result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId)) {
205801
+ bodyParts.push(NOTE_EXPAND_HINT);
205802
+ }
204649
205803
  const body = bodyParts.join(`
204650
205804
 
204651
205805
  `);
@@ -204659,7 +205813,7 @@ function createCtxSearchTool(deps) {
204659
205813
  args: {
204660
205814
  query: tool5.schema.string().describe("Search query. Matches against memory content, Primers, git commit messages, and raw user/assistant message text."),
204661
205815
  limit: tool5.schema.number().optional().describe("Maximum results to return (default: 10)"),
204662
- sources: tool5.schema.array(tool5.schema.enum(["memory", "message", "git_commit", "primer"])).optional().describe('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.')
205816
+ sources: tool5.schema.array(tool5.schema.enum(["memory", "message", "git_commit", "primer", "note"])).optional().describe('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.')
204663
205817
  },
204664
205818
  async execute(args, toolContext) {
204665
205819
  const query = args.query?.trim();
@@ -204691,7 +205845,7 @@ function createCtxSearchTool(deps) {
204691
205845
  visibleMemoryIds,
204692
205846
  explicitSearch: true
204693
205847
  });
204694
- return formatSearchResults(query, results);
205848
+ return formatSearchResults(query, results, toolContext.sessionID);
204695
205849
  }
204696
205850
  });
204697
205851
  }
@@ -204748,14 +205902,13 @@ function createToolRegistry(args) {
204748
205902
  return {};
204749
205903
  }
204750
205904
  ensureProjectRegisteredFromOpenCodeDirectory(ctx.directory, db);
204751
- const resolveProjectPath = (directory) => resolveProjectIdentity(directory);
204752
- const ctxReduceEnabled = pluginConfig.ctx_reduce_enabled !== false;
205905
+ const resolveProjectPath = (directory) => resolveProjectIdentityOrFallback(directory);
204753
205906
  const memoryEnabled = pluginConfig.memory?.enabled !== false;
204754
205907
  const allTools = {
204755
- ...ctxReduceEnabled ? createCtxReduceTools({
205908
+ ...createCtxReduceTools({
204756
205909
  db,
204757
205910
  protectedTags: pluginConfig.protected_tags ?? DEFAULT_PROTECTED_TAGS
204758
- }) : {},
205911
+ }),
204759
205912
  ...createCtxExpandTools({ db }),
204760
205913
  ...createCtxNoteTools({
204761
205914
  db,
@@ -205198,7 +206351,7 @@ var server = async (ctx) => {
205198
206351
  const dreamerRunnable = isDreamerRunnable(pluginConfig);
205199
206352
  const timerRegistration = {
205200
206353
  directory: ctx.directory,
205201
- projectIdentity: resolveProjectIdentity(ctx.directory),
206354
+ projectIdentity: resolveProjectIdentityOrFallback(ctx.directory),
205202
206355
  client: ctx.client,
205203
206356
  dreamerConfig: dreamerRunnable ? pluginConfig.dreamer : undefined,
205204
206357
  language: pluginConfig.language,