@cortexkit/pi-magic-context 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AGE_RECLAIM_MIN_TOKENS,
3
- CANONICAL_DREAM_TASKS,
3
+ BoundedSessionMap,
4
+ COMPACTION_ENABLED_PATH,
4
5
  COMPARTMENT_LEASE_RENEWAL_MS,
5
6
  COMPARTMENT_RENDER_EPOCH,
6
7
  DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE,
@@ -11,6 +12,8 @@ import {
11
12
  Database,
12
13
  GIT_SWEEP_LEASE_RENEWAL_MS,
13
14
  GLOBAL_USER_PROFILE_PROJECT_PATH,
15
+ LATEST_SUPPORTED_VERSION,
16
+ MAX_EXECUTE_THRESHOLD,
14
17
  PRIMER_CANDIDATE_MAX_AGE_MS,
15
18
  PRIMER_CANDIDATE_TTL_MS,
16
19
  ProjectIdentityError,
@@ -58,12 +61,12 @@ import {
58
61
  clearInjectionCache,
59
62
  clearMemoryVerifications,
60
63
  clearPendingCompactionMarkerStateIf,
64
+ clearPendingOps,
61
65
  clearPendingPiCompactionMarkerStateIf,
62
66
  clearPersistedReasoningWatermark,
63
67
  clearPersistedTodoSyntheticAnchor,
64
68
  clearRecompStaging,
65
69
  closeQuietly,
66
- compareTaskOrder,
67
70
  computeCueContentHash,
68
71
  computeNormalizedHash,
69
72
  computeWorkspaceEpochFingerprint,
@@ -91,6 +94,7 @@ import {
91
94
  encodeCachedM0UpgradeIdentity,
92
95
  enqueueShadowEmbeddingItems,
93
96
  ensureProjectRegisteredFromPiDirectory,
97
+ escalationBands,
94
98
  escapeXmlAttr,
95
99
  escapeXmlContent,
96
100
  estimateTokens,
@@ -111,6 +115,7 @@ import {
111
115
  getAutoSearchHintDecisions,
112
116
  getCachedAbsoluteMessageCount,
113
117
  getChannel2NudgeState,
118
+ getCompactionModeRecord,
114
119
  getCompartments,
115
120
  getCompartmentsByEndMessageId,
116
121
  getContextStoreUuid,
@@ -143,6 +148,7 @@ import {
143
148
  getMemoryVerifications,
144
149
  getMessageIndexReconciliationStartOrdinal,
145
150
  getMessageIndexSourceIdentity,
151
+ getMigrationOnOpenRefusal,
146
152
  getModuleNoteEvaluationBridge,
147
153
  getMuralCueState,
148
154
  getNoteLastReadAt,
@@ -150,14 +156,15 @@ import {
150
156
  getNotes,
151
157
  getNullOwnerToolTag,
152
158
  getOldestActiveUnprotectedToolTags,
159
+ getOpenCodeDbPath,
153
160
  getOrCreateSessionMeta,
154
161
  getOverflowState,
155
162
  getPendingCompactionMarkerState,
156
163
  getPendingOps,
157
164
  getPendingPiCompactionMarkerState,
158
165
  getPendingSmartNotes,
159
- getPersistedCompactionMarkerState,
160
166
  getPersistedNoteNudge,
167
+ getPersistedSchemaVersion,
161
168
  getPersistedTodoSyntheticAnchor,
162
169
  getPersistedToolTagAccounting,
163
170
  getPrimerCandidatesByIds,
@@ -205,18 +212,17 @@ import {
205
212
  insertUserMemory,
206
213
  insertUserMemoryCandidates,
207
214
  invalidateMemory,
208
- isCanonicalDreamTask,
215
+ isCompactionEnabled,
209
216
  isCompartmentLeaseHeld,
210
217
  isDreamerRunnable,
211
218
  isEditTool,
212
219
  isMessageIndexReconciledThrough,
213
220
  isMessageIndexSourceCurrent,
221
+ isMidTurn,
214
222
  isMidTurnPi,
215
223
  isRecord,
216
224
  isSaneLimit,
217
225
  isWrapupInProgress,
218
- leaseKeyFor,
219
- leaseKindFor,
220
226
  loadPiConfig,
221
227
  loadProtectedTailMeta,
222
228
  markGitSweepSuccessAndRelease,
@@ -233,6 +239,7 @@ import {
233
239
  openDatabase,
234
240
  openDatabaseAsync,
235
241
  parkGitSweepNonIndexable,
242
+ parseCompartmentOutput,
236
243
  parseCron,
237
244
  parseTodos,
238
245
  peekDeferredExecutePending,
@@ -286,7 +293,9 @@ import {
286
293
  reserveProtectedTailDrainTokens,
287
294
  resetLastNudgeCycle,
288
295
  resetLastNudgeCycleIfTailShrank,
296
+ resolveCompactionModeRecord,
289
297
  resolveGitTopLevel,
298
+ resolveLimit,
290
299
  resolveModelRefForPi,
291
300
  resolveMuralWire,
292
301
  resolvePiStableId,
@@ -302,6 +311,7 @@ import {
302
311
  saveSourceContent,
303
312
  scheduleAfterBootQuiet,
304
313
  setChannel2NudgeState,
314
+ setCompactionModeRecord,
305
315
  setDeferredExecutePendingIfAbsent,
306
316
  setEmergencyDropSample,
307
317
  setLastNudgeLevel,
@@ -309,7 +319,6 @@ import {
309
319
  setMemoryClassification,
310
320
  setMuralCue,
311
321
  setPendingPiCompactionMarkerState,
312
- setPersistedCompactionMarkerState,
313
322
  setPersistedNoteNudgeTrigger,
314
323
  setPersistedNoteNudgeTriggerMessageId,
315
324
  setPersistedTodoSyntheticAnchor,
@@ -317,6 +326,7 @@ import {
317
326
  setRecompPartialRange,
318
327
  setSessionWorkMetrics,
319
328
  setSqlitePragmaConfig,
329
+ setStoragePrivatePermissionEnforcement,
320
330
  setTodoSnapshot,
321
331
  sourceNameForMemory,
322
332
  stableStringify,
@@ -333,6 +343,7 @@ import {
333
343
  trimWorkspaceMemoriesToBudgetV2,
334
344
  unifiedSearch,
335
345
  updateCavemanDepth,
346
+ updateCompactionMarkerAfterPublication,
336
347
  updatePrimerAnswer,
337
348
  updatePrimerCandidateEmbedding,
338
349
  updatePrimerSupport,
@@ -350,10 +361,12 @@ import {
350
361
  withMigrationLanguageDirective,
351
362
  withRawMessageProvider,
352
363
  withRawSessionMessageCache
353
- } from "./index-f3jfb2ed.js";
364
+ } from "./index-0k3j1tc3.js";
365
+ import {
366
+ pushNotification
367
+ } from "./index-hw34xmzk.js";
354
368
  import {
355
369
  ensureCortexKitArtifactGitignore,
356
- getDataDir,
357
370
  getHarness,
358
371
  getMagicContextStorageDir,
359
372
  getProjectMagicContextHistorianDir,
@@ -368,7 +381,7 @@ import {
368
381
 
369
382
  // src/index.ts
370
383
  import { createRequire as createRequire3 } from "node:module";
371
- import { join as join6, resolve as resolve2 } from "node:path";
384
+ import { join as join5, resolve as resolve2 } from "node:path";
372
385
 
373
386
  // ../plugin/src/shared/resolve-fallbacks.ts
374
387
  function resolveFallbackChain(userFallbacks) {
@@ -415,6 +428,61 @@ function modelBodyField(spec) {
415
428
  return parsed ? { model: parsed } : {};
416
429
  }
417
430
 
431
+ // ../plugin/src/features/magic-context/dreamer/task-registry.ts
432
+ var CANONICAL_DREAM_TASKS = [
433
+ "map-memories",
434
+ "verify",
435
+ "verify-broad",
436
+ "curate",
437
+ "compress-cues",
438
+ "classify-memories",
439
+ "retrospective",
440
+ "maintain-docs",
441
+ "evaluate-smart-notes",
442
+ "review-user-memories",
443
+ "promote-primers",
444
+ "refresh-primers"
445
+ ];
446
+ var MEMORY_DOMAIN_TASKS = [
447
+ "map-memories",
448
+ "verify",
449
+ "verify-broad",
450
+ "curate",
451
+ "compress-cues",
452
+ "classify-memories",
453
+ "retrospective",
454
+ "promote-primers",
455
+ "refresh-primers"
456
+ ];
457
+ var MEMORY_DOMAIN_SET = new Set(MEMORY_DOMAIN_TASKS);
458
+ function leaseKindFor(task) {
459
+ if (MEMORY_DOMAIN_SET.has(task))
460
+ return "memory";
461
+ switch (task) {
462
+ case "review-user-memories":
463
+ return "user-memories";
464
+ case "promote-primers":
465
+ case "refresh-primers":
466
+ return "memory";
467
+ case "maintain-docs":
468
+ return "maintain-docs";
469
+ case "evaluate-smart-notes":
470
+ return "evaluate-smart-notes";
471
+ default:
472
+ return "memory";
473
+ }
474
+ }
475
+ function leaseKeyFor(task, projectIdentity) {
476
+ const kind = leaseKindFor(task);
477
+ return kind === "user-memories" ? "user-memories" : `${kind}:${projectIdentity}`;
478
+ }
479
+ function isCanonicalDreamTask(value) {
480
+ return CANONICAL_DREAM_TASKS.includes(value);
481
+ }
482
+ function compareTaskOrder(a, b) {
483
+ return CANONICAL_DREAM_TASKS.indexOf(a) - CANONICAL_DREAM_TASKS.indexOf(b);
484
+ }
485
+
418
486
  // ../plugin/src/features/magic-context/dreamer/task-config.ts
419
487
  function buildDreamTaskRuntimeConfigs(dreamer, language) {
420
488
  const tasks = dreamer.tasks ?? {};
@@ -478,14 +546,41 @@ function isMagicContextHiddenAgentName(agent) {
478
546
  return true;
479
547
  return false;
480
548
  }
549
+ var MAX_FORMATTED_BLOCKING_PROCESSES = 8;
550
+ function formatFailClosedBlockingProcesses(processes) {
551
+ const uniqueProcesses = new Map;
552
+ for (const process2 of processes) {
553
+ if (!process2.harness.trim() || !Number.isInteger(process2.pid) || process2.pid <= 0)
554
+ continue;
555
+ uniqueProcesses.set(`${process2.harness}\x00${process2.pid}`, {
556
+ harness: process2.harness.trim(),
557
+ pid: process2.pid
558
+ });
559
+ }
560
+ const entries = [...uniqueProcesses.values()];
561
+ const visible = entries.slice(0, MAX_FORMATTED_BLOCKING_PROCESSES);
562
+ const rendered = visible.map(({ harness, pid }) => `${harness} (PID ${pid})`);
563
+ const omitted = entries.length - visible.length;
564
+ if (omitted > 0)
565
+ rendered.push(`${omitted} more blocking process(es)`);
566
+ if (rendered.length === 0)
567
+ return "a live harness process";
568
+ if (rendered.length === 1)
569
+ return rendered[0];
570
+ const last = rendered.pop();
571
+ return `${rendered.join(", ")}, and ${last}`;
572
+ }
481
573
  function formatFailClosedBlockingMessage(reason) {
574
+ if (reason.kind === "migration_guard") {
575
+ return [
576
+ `Magic Context cannot migrate the shared database because ${formatFailClosedBlockingProcesses(reason.blockingProcesses)} may be running an older Magic Context build that would fail against the migrated database.`,
577
+ "Restart the blocking process (it will pick up the new build and migrate on start), or shut it down and retry.",
578
+ `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
579
+ ].join(" ");
580
+ }
482
581
  if (reason.kind === "schema_fence") {
483
582
  return [
484
- `Magic Context cannot operate: shared database schema v${reason.persistedVersion}`,
485
- `is newer than this build supports (max v${reason.supportedVersion}).`,
486
- "A newer OpenCode/Pi instance upgraded the database; this build fail-closed",
487
- "so it cannot corrupt the cache or silently fall back to native compaction.",
488
- `Update or unpin Magic Context on this harness, then restart.`,
583
+ `Magic Context cannot operate: this Magic Context build is older than the database; upgrade/restart this harness (database schema v${reason.persistedVersion}, build supports through v${reason.supportedVersion}).`,
489
584
  `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
490
585
  ].join(" ");
491
586
  }
@@ -546,7 +641,7 @@ function createFailClosedController(options) {
546
641
  const shouldReprobe = typeof input.tryReopen === "function" && (blockedPassCount === 1 || blockedPassCount % reprobeEveryN === 0);
547
642
  if (shouldReprobe) {
548
643
  try {
549
- const healed = await input.tryReopen();
644
+ const healed = await input.tryReopen?.();
550
645
  if (healed) {
551
646
  reason = null;
552
647
  blockedPassCount = 0;
@@ -729,6 +824,7 @@ var OVERFLOW_PATTERNS = [
729
824
  /maximum prompt length is \d+/i,
730
825
  /reduce the length of the messages/i,
731
826
  /maximum context length is \d+ tokens/i,
827
+ /maximum model length is \d+/i,
732
828
  /exceeds the limit of \d+/i,
733
829
  /exceeds the available context size/i,
734
830
  /greater than the context length/i,
@@ -744,15 +840,25 @@ var OVERFLOW_PATTERNS = [
744
840
  /context size has been exceeded/i
745
841
  ];
746
842
  var LIMIT_EXTRACTION_PATTERNS = [
747
- /maximum prompt length is (\d+)/i,
748
- /maximum context length is (\d+) tokens?/i,
749
- /context length is only (\d+) tokens?/i,
750
- /exceeds the limit of (\d+)/i,
751
- /too large for model with (\d+) maximum context length/i,
752
- /context size.*(\d+) tokens?/i,
753
- /exceeds? the context length of (\d+)/i,
754
- />\s*(\d+)\s*(?:tokens?\s*)?(?:maximum|max|limit)\b/i,
755
- /max(?:imum)?.*context.*?(\d+)/i
843
+ { pattern: /maximum prompt length is (\d+)/i, provenance: "prompt_only" },
844
+ {
845
+ pattern: /maximum context length is (\d+) tokens?/i,
846
+ provenance: "combined"
847
+ },
848
+ { pattern: /maximum model length is (\d+)/i, provenance: "combined" },
849
+ { pattern: /context length is only (\d+) tokens?/i, provenance: "combined" },
850
+ { pattern: /exceeds the limit of (\d+)/i, provenance: "unknown" },
851
+ {
852
+ pattern: /too large for model with (\d+) maximum context length/i,
853
+ provenance: "combined"
854
+ },
855
+ { pattern: /context size.*(\d+) tokens?/i, provenance: "combined" },
856
+ { pattern: /exceeds? the context length of (\d+)/i, provenance: "combined" },
857
+ {
858
+ pattern: />\s*(\d+)\s*(?:tokens?\s*)?(?:maximum|max|limit)\b/i,
859
+ provenance: "prompt_only"
860
+ },
861
+ { pattern: /max(?:imum)?.*context.*?(\d+)/i, provenance: "unknown" }
756
862
  ];
757
863
  var MIN_PLAUSIBLE_LIMIT = 1024;
758
864
  var MAX_PLAUSIBLE_LIMIT = 1e7;
@@ -803,14 +909,15 @@ function detectOverflow(error) {
803
909
  const reportedLimit = parseReportedLimit(message);
804
910
  return {
805
911
  isOverflow: true,
806
- reportedLimit,
912
+ reportedLimit: reportedLimit?.value,
913
+ reportedLimitProvenance: reportedLimit?.provenance,
807
914
  matchedPattern: matched?.source
808
915
  };
809
916
  }
810
917
  function parseReportedLimit(message) {
811
918
  if (!message)
812
919
  return;
813
- for (const pattern of LIMIT_EXTRACTION_PATTERNS) {
920
+ for (const { pattern, provenance } of LIMIT_EXTRACTION_PATTERNS) {
814
921
  const match = message.match(pattern);
815
922
  if (!match)
816
923
  continue;
@@ -822,7 +929,7 @@ function parseReportedLimit(message) {
822
929
  continue;
823
930
  if (value < MIN_PLAUSIBLE_LIMIT || value > MAX_PLAUSIBLE_LIMIT)
824
931
  continue;
825
- return value;
932
+ return { value, provenance };
826
933
  }
827
934
  return;
828
935
  }
@@ -1304,6 +1411,15 @@ async function runDeferredV22Backfill(db, options = {}) {
1304
1411
  };
1305
1412
  }
1306
1413
 
1414
+ // ../plugin/src/hooks/magic-context/ctx-reduce-availability.ts
1415
+ var ctxReduceRegisteredGlobally = true;
1416
+ function setCtxReduceRegisteredGlobally(registered) {
1417
+ ctxReduceRegisteredGlobally = registered;
1418
+ }
1419
+ var availabilityBySession = new BoundedSessionMap(1000);
1420
+ var permissionDeniedBySession = new BoundedSessionMap(2000);
1421
+ var ctxReducePermissionDenyLogged = new BoundedSessionMap(1000);
1422
+
1307
1423
  // ../plugin/src/hooks/magic-context/derive-budgets.ts
1308
1424
  var TRIGGER_BUDGET_PERCENTAGE = 0.05;
1309
1425
  var TRIGGER_BUDGET_MIN = 5000;
@@ -1347,7 +1463,6 @@ function resolveHistorianContextLimit(historianModelOverride) {
1347
1463
 
1348
1464
  // ../plugin/src/hooks/magic-context/event-resolvers.ts
1349
1465
  var DEFAULT_CONTEXT_LIMIT = 128000;
1350
- var MAX_EXECUTE_THRESHOLD = 80;
1351
1466
  function resolveCacheTtl(cacheTtl, modelKey) {
1352
1467
  if (typeof cacheTtl === "string") {
1353
1468
  return cacheTtl;
@@ -1392,7 +1507,7 @@ function resolveExecuteThresholdDetail(config, modelKey, fallback, options) {
1392
1507
  const dedupeKey = `${options.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${tokenMatch.value}|${cap}`;
1393
1508
  if (!clampWarnSeen.has(dedupeKey)) {
1394
1509
  clampWarnSeen.add(dedupeKey);
1395
- const msg = `execute_threshold_tokens clamped: ${tokenMatch.value} → ${effectiveTokens} (80% of ${contextLimit}) for ${modelKey ?? "default"}`;
1510
+ const msg = `execute_threshold_tokens clamped: ${tokenMatch.value} → ${effectiveTokens} (${MAX_EXECUTE_THRESHOLD}% of ${contextLimit}) for ${modelKey ?? "default"}`;
1396
1511
  if (options.sessionId) {
1397
1512
  sessionLog(options.sessionId, `WARN: ${msg}`);
1398
1513
  } else {
@@ -1448,7 +1563,7 @@ function resolveExecuteThresholdDetail(config, modelKey, fallback, options) {
1448
1563
  const dedupeKey = `pct|${options?.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${resolved}`;
1449
1564
  if (!clampWarnSeen.has(dedupeKey)) {
1450
1565
  clampWarnSeen.add(dedupeKey);
1451
- const msg = `execute_threshold clamped ${resolved}% → ${MAX_EXECUTE_THRESHOLD}% for ${modelKey ?? "default"} (capped for cache safety; a large step can overflow before compaction, and 80% stays below the 85%/95% emergency bands)`;
1566
+ const msg = `execute_threshold clamped ${resolved}% → ${MAX_EXECUTE_THRESHOLD}% for ${modelKey ?? "default"} (capped against the output-reserved safe window; 10% remains for mid-turn growth before the absolute 95% wall)`;
1452
1567
  if (options?.sessionId) {
1453
1568
  sessionLog(options.sessionId, `WARN: ${msg}`);
1454
1569
  } else {
@@ -1940,6 +2055,57 @@ import {
1940
2055
  import { createInterface } from "node:readline";
1941
2056
  import { fileURLToPath } from "node:url";
1942
2057
 
2058
+ // ../plugin/src/features/magic-context/schema-fence-probe.ts
2059
+ var STALE_CHILD_SPAWN_FAILURE = "stale_schema_fence";
2060
+ var STALE_CHILD_SPAWN_LATCH_THRESHOLD = 2;
2061
+ var state = {
2062
+ consecutiveFailures: 0,
2063
+ totalFailures: 0,
2064
+ latched: false,
2065
+ noticeEmitted: false,
2066
+ failure: null
2067
+ };
2068
+ function recordStaleFence(persistedVersion, supportedVersion) {
2069
+ state.consecutiveFailures += 1;
2070
+ state.totalFailures += 1;
2071
+ const latched = state.consecutiveFailures >= STALE_CHILD_SPAWN_LATCH_THRESHOLD;
2072
+ state.latched ||= latched;
2073
+ const failure = {
2074
+ failureClass: STALE_CHILD_SPAWN_FAILURE,
2075
+ persistedVersion,
2076
+ supportedVersion,
2077
+ consecutiveFailures: state.consecutiveFailures,
2078
+ totalFailures: state.totalFailures,
2079
+ latched: state.latched
2080
+ };
2081
+ state.failure = failure;
2082
+ const shouldSurface = state.latched && !state.noticeEmitted;
2083
+ if (shouldSurface)
2084
+ state.noticeEmitted = true;
2085
+ return { allowSpawn: false, failure, shouldSurface };
2086
+ }
2087
+ function probeChildSpawnFence(db) {
2088
+ if (!db) {
2089
+ const knownRejection = getSchemaFenceRejection();
2090
+ if (knownRejection) {
2091
+ return recordStaleFence(knownRejection.persistedVersion, knownRejection.supportedVersion);
2092
+ }
2093
+ return { allowSpawn: true };
2094
+ }
2095
+ try {
2096
+ const persistedVersion = getPersistedSchemaVersion(db);
2097
+ if (persistedVersion > LATEST_SUPPORTED_VERSION) {
2098
+ return recordStaleFence(persistedVersion, LATEST_SUPPORTED_VERSION);
2099
+ }
2100
+ } catch {
2101
+ return { allowSpawn: true };
2102
+ }
2103
+ state.consecutiveFailures = 0;
2104
+ state.latched = false;
2105
+ state.noticeEmitted = false;
2106
+ return { allowSpawn: true };
2107
+ }
2108
+
1943
2109
  // ../plugin/src/features/magic-context/subagent-token-capture.ts
1944
2110
  function asNumber(value) {
1945
2111
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
@@ -2288,6 +2454,10 @@ class PiSubagentRunner {
2288
2454
  if (ZERO_TOOL_PROMPT_REQUIRED_AGENTS.has(options.agent) && options.systemPrompt.trim().length === 0) {
2289
2455
  return failBeforeSpawn("invalid_prompt", `zero-tool Pi subagent "${options.agent}" requires a non-empty system prompt`, true);
2290
2456
  }
2457
+ const fence = probeChildSpawnFence(openDatabase());
2458
+ if (!fence.allowSpawn) {
2459
+ return failBeforeSpawn("spawn_failed", `Magic Context: plugin build is older than its database (database=v${fence.failure.persistedVersion}, supported_fence=v${fence.failure.supportedVersion}) — restart Pi.`);
2460
+ }
2291
2461
  const promptBytes = Buffer.byteLength(options.userMessage, "utf8");
2292
2462
  const deliverViaStdin = promptBytes > PROMPT_ARGV_MAX_BYTES || this.platform === "win32";
2293
2463
  let systemPromptTempDir;
@@ -2859,7 +3029,7 @@ function registerCtxAugCommand(pi, config) {
2859
3029
  sessionLog(sessionLabel, "/ctx-aug: spawning sidekick", {
2860
3030
  model: currentConfig.model
2861
3031
  });
2862
- const projectIdentity = resolveProjectIdentityForSession(ctx.cwd);
3032
+ const projectIdentity = resolveProjectIdentityForSession(ctx.cwd, currentConfig.allowHomeProject);
2863
3033
  if (!projectIdentity) {
2864
3034
  sessionLog(sessionLabel, "Error: Could not resolve project identity for sidekick.");
2865
3035
  return;
@@ -2903,264 +3073,10 @@ ${sidekickText}
2903
3073
  }
2904
3074
 
2905
3075
  // ../plugin/src/features/magic-context/dreamer/open-opencode-db.ts
2906
- import { existsSync as existsSync5 } from "node:fs";
2907
-
2908
- // ../plugin/src/features/magic-context/compaction-marker.ts
2909
- import { createHash as createHash2 } from "node:crypto";
2910
3076
  import { existsSync as existsSync4 } from "node:fs";
2911
- import { join as join3 } from "node:path";
2912
- var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2913
- var ID_PREFIX_HEX_LENGTH = 12;
2914
- var ID_SUFFIX_LENGTH = 14;
2915
- var ID_PREFIX_MASK = (1n << BigInt(ID_PREFIX_HEX_LENGTH * 4)) - 1n;
2916
- function deterministicBase62(seed, length) {
2917
- let value = BigInt(`0x${createHash2("sha256").update(seed).digest("hex")}`);
2918
- const chars = Array(length);
2919
- for (let index = length - 1;index >= 0; index -= 1) {
2920
- chars[index] = BASE62_CHARS[Number(value % 62n)];
2921
- value /= 62n;
2922
- }
2923
- return chars.join("");
2924
- }
2925
- function generateId(prefix, timestampMs, counter, identity) {
2926
- const encoded = BigInt(Math.max(0, Math.floor(timestampMs))) * 0x1000n + counter & ID_PREFIX_MASK;
2927
- const hex = encoded.toString(16).padStart(ID_PREFIX_HEX_LENGTH, "0");
2928
- return `${prefix}_${hex}${deterministicBase62(`${prefix}\x00${identity}`, ID_SUFFIX_LENGTH)}`;
2929
- }
2930
- function generateMessageId(timestampMs, counter = 0n, identity = "") {
2931
- return generateId("msg", timestampMs, counter, identity);
2932
- }
2933
- function generatePartId(timestampMs, counter = 0n, identity = "") {
2934
- return generateId("prt", timestampMs, counter, identity);
2935
- }
2936
- function getOpenCodeDbPath() {
2937
- return join3(getDataDir(), "opencode", "opencode.db");
2938
- }
2939
- var cachedWriteDb = null;
2940
- var REQUIRED_MESSAGE_COLUMNS = ["id", "session_id", "time_created", "time_updated", "data"];
2941
- var REQUIRED_PART_COLUMNS = [
2942
- "id",
2943
- "message_id",
2944
- "session_id",
2945
- "time_created",
2946
- "time_updated",
2947
- "data"
2948
- ];
2949
- var cachedSchemaCompatible = null;
2950
- function isOpenCodeSchemaCompatible(db, dbPath) {
2951
- if (cachedSchemaCompatible?.path === dbPath) {
2952
- return cachedSchemaCompatible.compatible;
2953
- }
2954
- try {
2955
- const messageCols = new Set(db.prepare("PRAGMA table_info(message)").all().map((r) => r.name ?? "").filter((n) => n.length > 0));
2956
- const partCols = new Set(db.prepare("PRAGMA table_info(part)").all().map((r) => r.name ?? "").filter((n) => n.length > 0));
2957
- const missingMessage = REQUIRED_MESSAGE_COLUMNS.filter((c) => !messageCols.has(c));
2958
- const missingPart = REQUIRED_PART_COLUMNS.filter((c) => !partCols.has(c));
2959
- if (missingMessage.length > 0 || missingPart.length > 0) {
2960
- log(`[magic-context] compaction-marker: OpenCode DB schema missing required columns ` + `(message: [${missingMessage.join(", ")}], part: [${missingPart.join(", ")}]). ` + `Marker injection disabled for this process. ` + `This usually means OpenCode was updated and magic-context is out of date.`);
2961
- cachedSchemaCompatible = { path: dbPath, compatible: false };
2962
- return false;
2963
- }
2964
- cachedSchemaCompatible = { path: dbPath, compatible: true };
2965
- return true;
2966
- } catch (error) {
2967
- log(`[magic-context] compaction-marker: schema probe failed: ${error instanceof Error ? error.message : String(error)}. ` + `Marker injection disabled until next process restart.`);
2968
- cachedSchemaCompatible = { path: dbPath, compatible: false };
2969
- return false;
2970
- }
2971
- }
2972
- function getWritableOpenCodeDb() {
2973
- const dbPath = getOpenCodeDbPath();
2974
- if (cachedWriteDb?.path === dbPath) {
2975
- return cachedWriteDb.db;
2976
- }
2977
- if (cachedWriteDb) {
2978
- try {
2979
- closeQuietly(cachedWriteDb.db);
2980
- } catch {}
2981
- }
2982
- if (!existsSync4(dbPath)) {
2983
- throw new Error(`OpenCode database not found at ${dbPath} (is OpenCode installed?)`);
2984
- }
2985
- const db = new Database(dbPath);
2986
- db.exec("PRAGMA busy_timeout=5000");
2987
- db.exec("PRAGMA journal_mode=WAL");
2988
- cachedWriteDb = { path: dbPath, db };
2989
- return db;
2990
- }
2991
- function getNonSummaryMessageSortKey(sessionId, messageId) {
2992
- const db = getWritableOpenCodeDb();
2993
- const row = db.prepare(`SELECT time_created, id
2994
- FROM message
2995
- WHERE session_id = ?
2996
- AND id = ?
2997
- AND NOT (COALESCE(json_extract(data, '$.summary'), 0) = 1
2998
- AND COALESCE(json_extract(data, '$.finish'), '') = 'stop')
2999
- LIMIT 1`).get(sessionId, messageId);
3000
- if (typeof row?.time_created !== "number" || typeof row.id !== "string") {
3001
- return null;
3002
- }
3003
- return { id: row.id, timeCreated: row.time_created };
3004
- }
3005
- function findBoundaryUserMessage(sessionId, endMessageId) {
3006
- const db = getWritableOpenCodeDb();
3007
- const target = getNonSummaryMessageSortKey(sessionId, endMessageId);
3008
- if (!target)
3009
- return null;
3010
- const boundary = db.prepare(`SELECT id, time_created, data
3011
- FROM message
3012
- WHERE session_id = ?
3013
- AND NOT (COALESCE(json_extract(data, '$.summary'), 0) = 1
3014
- AND COALESCE(json_extract(data, '$.finish'), '') = 'stop')
3015
- AND COALESCE(json_extract(data, '$.role'), '') = 'user'
3016
- AND (time_created < ? OR (time_created = ? AND id <= ?))
3017
- ORDER BY time_created DESC, id DESC
3018
- LIMIT 1`).get(sessionId, target.timeCreated, target.timeCreated, target.id);
3019
- if (typeof boundary?.id !== "string" || typeof boundary.time_created !== "number") {
3020
- return null;
3021
- }
3022
- return { id: boundary.id, timeCreated: boundary.time_created };
3023
- }
3024
- function compareOpenCodeMessagesByCanonicalOrder(sessionId, leftMessageId, rightMessageId) {
3025
- const left = getNonSummaryMessageSortKey(sessionId, leftMessageId);
3026
- const right = getNonSummaryMessageSortKey(sessionId, rightMessageId);
3027
- if (!left || !right)
3028
- return null;
3029
- if (left.timeCreated < right.timeCreated)
3030
- return -1;
3031
- if (left.timeCreated > right.timeCreated)
3032
- return 1;
3033
- if (left.id < right.id)
3034
- return -1;
3035
- if (left.id > right.id)
3036
- return 1;
3037
- return 0;
3038
- }
3039
- function removeLegacyMarkerLineageRows(db, args) {
3040
- const legacySummaries = db.prepare(`SELECT m.id
3041
- FROM message m
3042
- WHERE m.session_id = ?
3043
- AND m.id <> ?
3044
- AND COALESCE(json_extract(m.data, '$.summary'), 0) = 1
3045
- AND COALESCE(json_extract(m.data, '$.finish'), '') = 'stop'
3046
- AND COALESCE(json_extract(m.data, '$.parentID'), '') = ?
3047
- AND EXISTS (
3048
- SELECT 1
3049
- FROM part p
3050
- WHERE p.session_id = m.session_id
3051
- AND p.message_id = m.id
3052
- AND COALESCE(json_extract(p.data, '$.type'), '') = 'text'
3053
- AND COALESCE(json_extract(p.data, '$.text'), '') = ?
3054
- )`).all(args.sessionId, args.summaryMessageId, args.boundaryMessageId, args.summaryText);
3055
- const legacySummaryIds = legacySummaries.flatMap((row) => typeof row.id === "string" ? [row.id] : []);
3056
- if (legacySummaryIds.length === 0)
3057
- return;
3058
- const deleteSummaryParts = db.prepare("DELETE FROM part WHERE session_id = ? AND message_id = ?");
3059
- const deleteSummary = db.prepare("DELETE FROM message WHERE session_id = ? AND id = ?");
3060
- for (const summaryMessageId of legacySummaryIds) {
3061
- deleteSummaryParts.run(args.sessionId, summaryMessageId);
3062
- deleteSummary.run(args.sessionId, summaryMessageId);
3063
- }
3064
- db.prepare(`DELETE FROM part
3065
- WHERE session_id = ?
3066
- AND message_id = ?
3067
- AND id <> ?
3068
- AND COALESCE(json_extract(data, '$.type'), '') = 'compaction'
3069
- AND COALESCE(json_extract(data, '$.auto'), 0) = 1`).run(args.sessionId, args.boundaryMessageId, args.compactionPartId);
3070
- }
3071
- function injectCompactionMarker(args) {
3072
- const db = getWritableOpenCodeDb();
3073
- if (!isOpenCodeSchemaCompatible(db, getOpenCodeDbPath())) {
3074
- return null;
3075
- }
3076
- const boundary = args.resolvedBoundary ?? findBoundaryUserMessage(args.sessionId, args.endMessageId);
3077
- if (!boundary) {
3078
- log(`[magic-context] compaction-marker: no user message found at or before endMessageId ${args.endMessageId} (ordinal ${args.endOrdinal})`);
3079
- return null;
3080
- }
3081
- const boundaryTime = boundary.timeCreated;
3082
- const markerIdentity = `${args.sessionId}\x00${args.endMessageId}`;
3083
- const summaryMsgId = generateMessageId(boundaryTime + 1, 1n, `${markerIdentity}\x00summary-message`);
3084
- const compactionPartId = generatePartId(boundaryTime, 1n, `${markerIdentity}\x00compaction-part`);
3085
- const summaryPartId = generatePartId(boundaryTime + 1, 2n, `${markerIdentity}\x00summary-part`);
3086
- const summaryMsgData = JSON.stringify({
3087
- role: "assistant",
3088
- parentID: boundary.id,
3089
- summary: true,
3090
- finish: "stop",
3091
- mode: "compaction",
3092
- agent: "compaction",
3093
- path: { cwd: args.directory, root: args.directory },
3094
- cost: 0,
3095
- tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
3096
- modelID: "magic-context",
3097
- providerID: "magic-context",
3098
- time: { created: boundaryTime + 1 }
3099
- });
3100
- try {
3101
- db.transaction(() => {
3102
- removeLegacyMarkerLineageRows(db, {
3103
- sessionId: args.sessionId,
3104
- boundaryMessageId: boundary.id,
3105
- summaryText: args.summaryText,
3106
- summaryMessageId: summaryMsgId,
3107
- compactionPartId
3108
- });
3109
- db.prepare(`INSERT INTO part (id, message_id, session_id, time_created, time_updated, data)
3110
- VALUES (?, ?, ?, ?, ?, ?)
3111
- ON CONFLICT(id) DO UPDATE SET
3112
- message_id = excluded.message_id,
3113
- session_id = excluded.session_id,
3114
- time_created = excluded.time_created,
3115
- time_updated = excluded.time_updated,
3116
- data = excluded.data`).run(compactionPartId, boundary.id, args.sessionId, boundaryTime, boundaryTime, '{"type":"compaction","auto":true}');
3117
- db.prepare(`INSERT INTO message (id, session_id, time_created, time_updated, data)
3118
- VALUES (?, ?, ?, ?, ?)
3119
- ON CONFLICT(id) DO UPDATE SET
3120
- session_id = excluded.session_id,
3121
- time_created = excluded.time_created,
3122
- time_updated = excluded.time_updated,
3123
- data = excluded.data`).run(summaryMsgId, args.sessionId, boundaryTime + 1, boundaryTime + 1, summaryMsgData);
3124
- db.prepare(`INSERT INTO part (id, message_id, session_id, time_created, time_updated, data)
3125
- VALUES (?, ?, ?, ?, ?, ?)
3126
- ON CONFLICT(id) DO UPDATE SET
3127
- message_id = excluded.message_id,
3128
- session_id = excluded.session_id,
3129
- time_created = excluded.time_created,
3130
- time_updated = excluded.time_updated,
3131
- data = excluded.data`).run(summaryPartId, summaryMsgId, args.sessionId, boundaryTime + 1, boundaryTime + 1, JSON.stringify({ type: "text", text: args.summaryText }));
3132
- })();
3133
- log(`[magic-context] compaction-marker: injected boundary at user msg ${boundary.id} (ordinal ~${args.endOrdinal}), summary msg ${summaryMsgId}`);
3134
- return {
3135
- boundaryMessageId: boundary.id,
3136
- summaryMessageId: summaryMsgId,
3137
- compactionPartId,
3138
- summaryPartId
3139
- };
3140
- } catch (error) {
3141
- log(`[magic-context] compaction-marker: injection failed: ${error instanceof Error ? error.message : String(error)}`);
3142
- return null;
3143
- }
3144
- }
3145
- function removeCompactionMarker(state) {
3146
- try {
3147
- const db = getWritableOpenCodeDb();
3148
- db.transaction(() => {
3149
- db.prepare("DELETE FROM part WHERE id = ?").run(state.summaryPartId);
3150
- db.prepare("DELETE FROM message WHERE id = ?").run(state.summaryMessageId);
3151
- db.prepare("DELETE FROM part WHERE id = ?").run(state.compactionPartId);
3152
- })();
3153
- return true;
3154
- } catch (error) {
3155
- log(`[magic-context] compaction-marker: removal failed: ${error instanceof Error ? error.message : String(error)}`);
3156
- return false;
3157
- }
3158
- }
3159
-
3160
- // ../plugin/src/features/magic-context/dreamer/open-opencode-db.ts
3161
3077
  function openOpenCodeDb() {
3162
3078
  const dbPath = getOpenCodeDbPath();
3163
- if (!existsSync5(dbPath)) {
3079
+ if (!existsSync4(dbPath)) {
3164
3080
  log(`[dreamer] OpenCode DB not found at ${dbPath} — skipping OpenCode history scan`);
3165
3081
  return null;
3166
3082
  }
@@ -3175,8 +3091,8 @@ function openOpenCodeDb() {
3175
3091
  }
3176
3092
 
3177
3093
  // ../plugin/src/features/magic-context/dreamer/task-executor.ts
3178
- import { createHash as createHash7 } from "node:crypto";
3179
- import { existsSync as existsSync8 } from "node:fs";
3094
+ import { createHash as createHash6 } from "node:crypto";
3095
+ import { existsSync as existsSync7 } from "node:fs";
3180
3096
 
3181
3097
  // ../plugin/src/agents/dreamer.ts
3182
3098
  var DREAMER_AGENT = "dreamer";
@@ -3186,6 +3102,201 @@ var DREAMER_MEMORY_MAPPER_AGENT = "dreamer-memory-mapper";
3186
3102
  var DREAMER_CLASSIFIER_AGENT = "dreamer-classifier";
3187
3103
  var DREAMER_DOCS_AGENT = "dreamer-docs";
3188
3104
  var DREAMER_REVIEWER_AGENT = "dreamer-reviewer";
3105
+
3106
+ // ../plugin/src/hooks/magic-context/send-session-notification.ts
3107
+ var MAX_QUEUED_IGNORED_NOTIFICATIONS = 16;
3108
+ var queuedIgnoredNotifications = new Map;
3109
+ var flushingIgnoredNotifications = new Set;
3110
+ var midTurnDetector = (sessionId) => isMidTurn(undefined, sessionId);
3111
+ function queueIgnoredNotification(notification) {
3112
+ const queued = queuedIgnoredNotifications.get(notification.sessionId) ?? [];
3113
+ queued.push(notification);
3114
+ if (queued.length > MAX_QUEUED_IGNORED_NOTIFICATIONS) {
3115
+ queued.splice(0, queued.length - MAX_QUEUED_IGNORED_NOTIFICATIONS);
3116
+ sessionLog(notification.sessionId, `ignored notification queue full; dropped oldest entries (kept newest ${MAX_QUEUED_IGNORED_NOTIFICATIONS})`);
3117
+ }
3118
+ queuedIgnoredNotifications.set(notification.sessionId, queued);
3119
+ }
3120
+ async function trySendTuiToast(sessionId, text, params, forcePersist) {
3121
+ if (forcePersist)
3122
+ return false;
3123
+ const title = extractToastTitle(text);
3124
+ const message = text.length > 200 ? `${text.slice(0, 200)}…` : text;
3125
+ const toastVariant = inferToastVariant(text);
3126
+ const duration = params.toastDurationMs ?? 5000;
3127
+ const { isTuiConnected: checkTui } = await import("./rpc-notifications-s2rw51c6.js");
3128
+ if (!checkTui(sessionId))
3129
+ return false;
3130
+ try {
3131
+ const { pushNotification: pushNotification2 } = await import("./rpc-notifications-s2rw51c6.js");
3132
+ pushNotification2("toast", {
3133
+ title,
3134
+ message,
3135
+ variant: toastVariant,
3136
+ duration
3137
+ }, sessionId);
3138
+ return true;
3139
+ } catch {
3140
+ sessionLog(sessionId, "TUI RPC toast enqueue failed, falling back to ignored message");
3141
+ return false;
3142
+ }
3143
+ }
3144
+ function hasNotificationSessionClient(client) {
3145
+ if (client === null || typeof client !== "object")
3146
+ return false;
3147
+ const candidate = client;
3148
+ if (candidate.session === undefined)
3149
+ return true;
3150
+ if (candidate.session === null || typeof candidate.session !== "object")
3151
+ return false;
3152
+ const session = candidate.session;
3153
+ return (session.prompt === undefined || typeof session.prompt === "function") && (session.promptAsync === undefined || typeof session.promptAsync === "function");
3154
+ }
3155
+ function inferToastVariant(text) {
3156
+ const lower = text.toLowerCase();
3157
+ if (lower.includes("error") || lower.includes("failed") || lower.includes("alert"))
3158
+ return "error";
3159
+ if (lower.includes("warning") || lower.includes("⚠"))
3160
+ return "warning";
3161
+ if (lower.includes("complete") || lower.includes("success") || lower.includes("✓") || lower.includes("finished"))
3162
+ return "success";
3163
+ return "info";
3164
+ }
3165
+ function extractToastTitle(text) {
3166
+ const headingMatch = text.match(/^#+\s+(.+)/m);
3167
+ if (headingMatch)
3168
+ return headingMatch[1].trim();
3169
+ const firstLine = text.split(`
3170
+ `)[0].trim();
3171
+ if (firstLine.length <= 80)
3172
+ return firstLine;
3173
+ return "Magic Context";
3174
+ }
3175
+ async function sendIgnoredMessageNow(client, sessionId, text, params, forcePersist) {
3176
+ if (midTurnDetector(sessionId)) {
3177
+ queueIgnoredNotification({ client, sessionId, text, params, forcePersist });
3178
+ return "queued";
3179
+ }
3180
+ const { waitForSafeNotificationTarget } = await import("./safe-notification-target-ng8ygena.js");
3181
+ if (await waitForSafeNotificationTarget(client, sessionId) === "skip") {
3182
+ sessionLog(sessionId, "notification skipped (session not titled yet)");
3183
+ return "skipped";
3184
+ }
3185
+ if (midTurnDetector(sessionId)) {
3186
+ queueIgnoredNotification({ client, sessionId, text, params, forcePersist });
3187
+ return "queued";
3188
+ }
3189
+ if (!hasNotificationSessionClient(client)) {
3190
+ sessionLog(sessionId, "session prompt API unavailable for notification");
3191
+ return "failed";
3192
+ }
3193
+ const c = client;
3194
+ let agent = params.agent || undefined;
3195
+ let variant = params.variant || undefined;
3196
+ let model = params.providerId && params.modelId ? { providerID: params.providerId, modelID: params.modelId } : undefined;
3197
+ if (!agent || !model || !variant) {
3198
+ try {
3199
+ const { resolvePromptContext } = await import("./prompt-context-602yq94t.js");
3200
+ const resolved = await resolvePromptContext(client, sessionId);
3201
+ if (resolved) {
3202
+ agent = agent ?? resolved.agent;
3203
+ model = model ?? resolved.model;
3204
+ variant = variant ?? resolved.variant;
3205
+ }
3206
+ } catch {}
3207
+ }
3208
+ if (midTurnDetector(sessionId)) {
3209
+ queueIgnoredNotification({ client, sessionId, text, params, forcePersist });
3210
+ return "queued";
3211
+ }
3212
+ const input = {
3213
+ path: { id: sessionId },
3214
+ body: {
3215
+ noReply: true,
3216
+ agent,
3217
+ model,
3218
+ variant,
3219
+ parts: [
3220
+ {
3221
+ type: "text",
3222
+ text,
3223
+ ignored: true
3224
+ }
3225
+ ]
3226
+ }
3227
+ };
3228
+ try {
3229
+ if (typeof c.session?.prompt === "function") {
3230
+ await Promise.resolve(c.session.prompt(input));
3231
+ return "sent";
3232
+ }
3233
+ if (typeof c.session?.promptAsync === "function") {
3234
+ await c.session.promptAsync(input);
3235
+ return "sent";
3236
+ }
3237
+ sessionLog(sessionId, "session prompt API unavailable for notification");
3238
+ return "failed";
3239
+ } catch (error) {
3240
+ const msg = getErrorMessage(error);
3241
+ sessionLog(sessionId, "failed to send notification:", msg);
3242
+ return "failed";
3243
+ }
3244
+ }
3245
+ async function sendIgnoredMessage(client, sessionId, text, params, forcePersist = false) {
3246
+ if (await trySendTuiToast(sessionId, text, params, forcePersist))
3247
+ return "sent";
3248
+ if (midTurnDetector(sessionId)) {
3249
+ queueIgnoredNotification({ client, sessionId, text, params, forcePersist });
3250
+ return "queued";
3251
+ }
3252
+ return sendIgnoredMessageNow(client, sessionId, text, params, forcePersist);
3253
+ }
3254
+
3255
+ // ../plugin/src/hooks/magic-context/child-session-spawn.ts
3256
+ var STALE_PLUGIN_RESTART_NOTICE = "Magic Context: plugin build is older than its database — restart OpenCode";
3257
+ async function surfaceStalePluginBuild(args) {
3258
+ if (!args.parentSessionId)
3259
+ return;
3260
+ try {
3261
+ if (args.db) {
3262
+ updateSessionMeta(args.db, args.parentSessionId, {
3263
+ lastTransformError: STALE_PLUGIN_RESTART_NOTICE
3264
+ });
3265
+ }
3266
+ pushNotification("toast", {
3267
+ title: "Magic Context",
3268
+ message: STALE_PLUGIN_RESTART_NOTICE,
3269
+ variant: "error",
3270
+ duration: 1e4
3271
+ }, args.parentSessionId);
3272
+ pushNotification("action", { action: "refresh-sidebar" }, args.parentSessionId);
3273
+ await sendIgnoredMessage(args.client, args.parentSessionId, STALE_PLUGIN_RESTART_NOTICE, args.notificationParams ?? {}, true);
3274
+ } catch (error) {
3275
+ sessionLog(args.parentSessionId, `stale plugin-build warning delivery failed: ${error instanceof Error ? error.message : String(error)}`);
3276
+ }
3277
+ }
3278
+ async function createChildSessionWithFence(args) {
3279
+ const verdict = probeChildSpawnFence(args.db);
3280
+ if (!verdict.allowSpawn) {
3281
+ if (args.parentSessionId) {
3282
+ sessionLog(args.parentSessionId, `child session skipped (${verdict.failure.failureClass}): database=v${verdict.failure.persistedVersion}, supported_fence=v${verdict.failure.supportedVersion}, consecutive=${verdict.failure.consecutiveFailures}, total=${verdict.failure.totalFailures}`);
3283
+ }
3284
+ if (verdict.shouldSurface) {
3285
+ if (args.onFenceLatched)
3286
+ await args.onFenceLatched(verdict.failure);
3287
+ else
3288
+ await surfaceStalePluginBuild(args);
3289
+ }
3290
+ return null;
3291
+ }
3292
+ return args.client.session.create({
3293
+ body: {
3294
+ ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
3295
+ title: args.title
3296
+ },
3297
+ query: { directory: args.directory }
3298
+ });
3299
+ }
3189
3300
  // ../plugin/src/shared/model-suggestion-retry.ts
3190
3301
  var ABORT_CALL_TIMEOUT_MS = 3000;
3191
3302
  function copyPromptArgs(args, body) {
@@ -3839,7 +3950,7 @@ function parseCuesManifest(text) {
3839
3950
  const body = extractCompleteManifestBody(text, "cues");
3840
3951
  const out = [];
3841
3952
  for (const match of body.matchAll(/<cue\s+id="(\d+)"\s*>([\s\S]*?)<\/cue>/g)) {
3842
- const id = Number.parseInt(match[1], 10);
3953
+ const id = Number.parseInt(match[1] ?? "", 10);
3843
3954
  if (!Number.isInteger(id))
3844
3955
  continue;
3845
3956
  out.push({ id, cue: unescapeXml(match[2] ?? "").trim() });
@@ -3940,7 +4051,10 @@ async function runCompressCues(args) {
3940
4051
  break;
3941
4052
  }
3942
4053
  const sliceMs = computeChunkSliceMs(remainingMs, chunks.length - i);
3943
- const outcome = await compressOneChunk(args, chunks[i], sliceMs, abortController.signal);
4054
+ const chunk = chunks[i];
4055
+ if (!chunk)
4056
+ break;
4057
+ const outcome = await compressOneChunk(args, chunk, sliceMs, abortController.signal);
3944
4058
  result.compressed += outcome.compressed;
3945
4059
  result.skipped += outcome.skipped;
3946
4060
  result.remaining -= outcome.compressed;
@@ -3975,12 +4089,12 @@ async function compressOneChunk(args, chunk, sliceMs, signal) {
3975
4089
  projectPath: args.projectIdentity,
3976
4090
  memories: chunk.map(toPromptMemory)
3977
4091
  });
3978
- const createResponse = await args.client.session.create({
3979
- body: {
3980
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
3981
- title: "magic-context-dream-compress-cues"
3982
- },
3983
- query: { directory: args.sessionDirectory }
4092
+ const createResponse = await createChildSessionWithFence({
4093
+ client: args.client,
4094
+ db: args.db,
4095
+ parentSessionId: args.parentSessionId,
4096
+ title: "magic-context-dream-compress-cues",
4097
+ directory: args.sessionDirectory
3984
4098
  });
3985
4099
  const created = normalizeSDKResponse(createResponse, null, {
3986
4100
  preferResponseOnMissingData: true
@@ -4464,12 +4578,12 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
4464
4578
  abortController.abort();
4465
4579
  });
4466
4580
  try {
4467
- const createResponse = await args.client.session.create({
4468
- body: {
4469
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
4470
- title: "magic-context-dream-user-memories"
4471
- },
4472
- query: { directory: args.sessionDirectory }
4581
+ const createResponse = await createChildSessionWithFence({
4582
+ client: args.client,
4583
+ db: args.db,
4584
+ parentSessionId: args.parentSessionId,
4585
+ title: "magic-context-dream-user-memories",
4586
+ directory: args.sessionDirectory
4473
4587
  });
4474
4588
  const created = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
4475
4589
  agentSessionId = typeof created?.id === "string" ? created.id : null;
@@ -4585,7 +4699,7 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
4585
4699
  }
4586
4700
 
4587
4701
  // ../plugin/src/features/magic-context/dreamer/classify.ts
4588
- import { createHash as createHash3 } from "node:crypto";
4702
+ import { createHash as createHash2 } from "node:crypto";
4589
4703
 
4590
4704
  // ../plugin/src/plugin/rust-tool-backends.ts
4591
4705
  function isRustAuthorityDrainingError(error) {
@@ -5063,12 +5177,12 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
5063
5177
  recordInvocation(args, startedAt, { status: "completed" });
5064
5178
  return run2;
5065
5179
  }
5066
- const createResponse = await args.client.session.create({
5067
- body: {
5068
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
5069
- title: "magic-context-dream-classify"
5070
- },
5071
- query: { directory: args.sessionDirectory }
5180
+ const createResponse = await createChildSessionWithFence({
5181
+ client: args.client,
5182
+ db: args.db,
5183
+ parentSessionId: args.parentSessionId,
5184
+ title: "magic-context-dream-classify",
5185
+ directory: args.sessionDirectory
5072
5186
  });
5073
5187
  const created = normalizeSDKResponse(createResponse, null, {
5074
5188
  preferResponseOnMissingData: true
@@ -5146,7 +5260,7 @@ async function runClassifyThroughModule(args, chunk, anchors, signal) {
5146
5260
  v: 1,
5147
5261
  session_id: args.moduleSessionId,
5148
5262
  task: "classify",
5149
- command_id: `classify:${args.moduleCommandId ?? Date.now()}:${createHash3("sha256").update(chunk.map((candidate) => candidate.id).join(",")).digest("hex").slice(0, 24)}`,
5263
+ command_id: `classify:${args.moduleCommandId ?? Date.now()}:${createHash2("sha256").update(chunk.map((candidate) => candidate.id).join(",")).digest("hex").slice(0, 24)}`,
5150
5264
  authority_generation: args.moduleAuthorityGeneration,
5151
5265
  payload: {
5152
5266
  prompt_body: prompt,
@@ -5842,7 +5956,7 @@ function throwIfAborted2(signal) {
5842
5956
  }
5843
5957
 
5844
5958
  // ../plugin/src/features/magic-context/smart-notes/compiler.ts
5845
- import { createHash as createHash4 } from "node:crypto";
5959
+ import { createHash as createHash3 } from "node:crypto";
5846
5960
 
5847
5961
  // ../plugin/src/features/magic-context/smart-notes/compiler-prompt.ts
5848
5962
  var SMART_NOTE_COMPILER_SYSTEM_PROMPT = `You are the Magic Context smart-note compiler for the magic-context system.
@@ -5880,7 +5994,7 @@ function getAsyncModule() {
5880
5994
  asyncModulePromise ??= (async () => {
5881
5995
  const [{ default: singlefileAsyncifyVariant }, { newQuickJSAsyncWASMModuleFromVariant }] = await Promise.all([
5882
5996
  import("./index-ayd60xnw.js").then((m)=>__toESM(m.default,1)),
5883
- import("./index-v7fmc1s2.js")
5997
+ import("./index-3pnd4qh9.js")
5884
5998
  ]);
5885
5999
  return newQuickJSAsyncWASMModuleFromVariant(singlefileAsyncifyVariant);
5886
6000
  })();
@@ -6099,12 +6213,12 @@ Remember: output only the JSON object described by the system prompt.`;
6099
6213
  });
6100
6214
  };
6101
6215
  try {
6102
- const createResponse = await args.client.session.create({
6103
- body: {
6104
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
6105
- title: `magic-context-smart-note-compile-${args.note.id}`
6106
- },
6107
- query: { directory: args.sessionDirectory ?? args.projectIdentity }
6216
+ const createResponse = await createChildSessionWithFence({
6217
+ client: args.client,
6218
+ db: args.db ?? null,
6219
+ parentSessionId: args.parentSessionId,
6220
+ title: `magic-context-smart-note-compile-${args.note.id}`,
6221
+ directory: args.sessionDirectory ?? args.projectIdentity
6108
6222
  });
6109
6223
  const created = normalizeSDKResponse(createResponse, null, {
6110
6224
  preferResponseOnMissingData: true
@@ -6266,7 +6380,7 @@ function manifestAdvisoryWarnings(code, manifest) {
6266
6380
  return warnings;
6267
6381
  }
6268
6382
  function hashCheck(surfaceCondition, compiledCheck, manifest, checkCron) {
6269
- return createHash4("sha256").update(surfaceCondition ?? "").update("\x00").update(compiledCheck).update("\x00").update(JSON.stringify(manifest)).update("\x00").update(checkCron).digest("hex");
6383
+ return createHash3("sha256").update(surfaceCondition ?? "").update("\x00").update(compiledCheck).update("\x00").update(JSON.stringify(manifest)).update("\x00").update(checkCron).digest("hex");
6270
6384
  }
6271
6385
  function extractJsonObject(output) {
6272
6386
  const fenced = output.match(/```(?:json)?\s*([\s\S]*?)```/i);
@@ -6885,12 +6999,12 @@ async function confirmReadOnly(args, noteId, content, surfaceCondition, leaseSig
6885
6999
  });
6886
7000
  };
6887
7001
  try {
6888
- const createResponse = await args.client.session.create({
6889
- body: {
6890
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
6891
- title: `magic-context-smart-note-confirm-${noteId}`
6892
- },
6893
- query: { directory: args.sessionDirectory ?? args.projectIdentity }
7002
+ const createResponse = await createChildSessionWithFence({
7003
+ client: args.client,
7004
+ db: args.db,
7005
+ parentSessionId: args.parentSessionId,
7006
+ title: `magic-context-smart-note-confirm-${noteId}`,
7007
+ directory: args.sessionDirectory ?? args.projectIdentity
6894
7008
  });
6895
7009
  const created = normalizeSDKResponse(createResponse, null, {
6896
7010
  preferResponseOnMissingData: true
@@ -6964,8 +7078,8 @@ Output exactly JSON: {"met": false}`;
6964
7078
  }
6965
7079
 
6966
7080
  // ../plugin/src/features/magic-context/dreamer/maintain-docs-protected-enforcement.ts
6967
- import { existsSync as existsSync6, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
6968
- import { join as join4 } from "node:path";
7081
+ import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
7082
+ import { join as join3 } from "node:path";
6969
7083
 
6970
7084
  // ../plugin/src/features/magic-context/dreamer/protected-regions.ts
6971
7085
  var PROTECTED_START_TOKEN = "mc:protected START";
@@ -7066,9 +7180,9 @@ var MAINTAIN_DOCS_SNAPSHOT_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
7066
7180
  function snapshotMaintainDocsFiles(docsDir) {
7067
7181
  const snapshot = new Map;
7068
7182
  for (const name of MAINTAIN_DOCS_SNAPSHOT_FILES) {
7069
- const path4 = join4(docsDir, name);
7183
+ const path4 = join3(docsDir, name);
7070
7184
  try {
7071
- if (existsSync6(path4)) {
7185
+ if (existsSync5(path4)) {
7072
7186
  snapshot.set(name, readFileSync2(path4, "utf8"));
7073
7187
  }
7074
7188
  } catch {}
@@ -7077,7 +7191,7 @@ function snapshotMaintainDocsFiles(docsDir) {
7077
7191
  }
7078
7192
  function enforceMaintainDocsProtectedRegions(args) {
7079
7193
  for (const [fileName, original] of args.snapshot) {
7080
- const path4 = join4(args.docsDir, fileName);
7194
+ const path4 = join3(args.docsDir, fileName);
7081
7195
  try {
7082
7196
  const current = readFileSync2(path4, "utf8");
7083
7197
  const { text, violated } = enforceProtectedRegions(original, current);
@@ -7093,10 +7207,10 @@ function enforceMaintainDocsProtectedRegions(args) {
7093
7207
  }
7094
7208
 
7095
7209
  // ../plugin/src/features/magic-context/dreamer/map-memories.ts
7096
- import { createHash as createHash5 } from "node:crypto";
7210
+ import { createHash as createHash4 } from "node:crypto";
7097
7211
 
7098
7212
  // ../plugin/src/features/magic-context/dreamer/map-memories-prompt.ts
7099
- import { existsSync as existsSync7, statSync } from "node:fs";
7213
+ import { existsSync as existsSync6, statSync } from "node:fs";
7100
7214
  import path4 from "node:path";
7101
7215
  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.
7102
7216
 
@@ -7134,7 +7248,7 @@ function extractMemoryCandidatePaths(content, repoDir) {
7134
7248
  if (!abs.startsWith(`${root}/`))
7135
7249
  continue;
7136
7250
  try {
7137
- if (existsSync7(abs) && statSync(abs).isFile())
7251
+ if (existsSync6(abs) && statSync(abs).isFile())
7138
7252
  found.add(rel);
7139
7253
  } catch {}
7140
7254
  if (found.size >= MAX_SEED_PATHS_PER_MEMORY)
@@ -7235,12 +7349,12 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
7235
7349
  let agentSessionId = null;
7236
7350
  const startedAt = Date.now();
7237
7351
  try {
7238
- const createResponse = await args.client.session.create({
7239
- body: {
7240
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
7241
- title: "magic-context-dream-map-memories"
7242
- },
7243
- query: { directory: args.sessionDirectory }
7352
+ const createResponse = await createChildSessionWithFence({
7353
+ client: args.client,
7354
+ db: args.db,
7355
+ parentSessionId: args.parentSessionId,
7356
+ title: "magic-context-dream-map-memories",
7357
+ directory: args.sessionDirectory
7244
7358
  });
7245
7359
  const created = normalizeSDKResponse(createResponse, null, {
7246
7360
  preferResponseOnMissingData: true
@@ -7353,7 +7467,7 @@ async function applyBatchMappings(args, batch, manifestText) {
7353
7467
  memory_project: args.projectIdentity,
7354
7468
  context_store_uuid: args.moduleRoute.moduleContextStoreUuid,
7355
7469
  authority_generation: args.moduleRoute.moduleAuthorityGeneration,
7356
- command_id: `${args.moduleRoute.moduleCommandId}:${createHash5("sha256").update(rows.map((row) => row.memory_id).join(",")).digest("hex").slice(0, 16)}`,
7470
+ command_id: `${args.moduleRoute.moduleCommandId}:${createHash4("sha256").update(rows.map((row) => row.memory_id).join(",")).digest("hex").slice(0, 16)}`,
7357
7471
  rows
7358
7472
  }
7359
7473
  }
@@ -7852,12 +7966,12 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
7852
7966
  let agentSessionId = null;
7853
7967
  const startedAt = Date.now();
7854
7968
  try {
7855
- const createResponse = await args.client.session.create({
7856
- body: {
7857
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
7858
- title: "magic-context-dream-refresh-primers"
7859
- },
7860
- query: { directory: args.sessionDirectory }
7969
+ const createResponse = await createChildSessionWithFence({
7970
+ client: args.client,
7971
+ db: args.db,
7972
+ parentSessionId: args.parentSessionId,
7973
+ title: "magic-context-dream-refresh-primers",
7974
+ directory: args.sessionDirectory
7861
7975
  });
7862
7976
  const created = normalizeSDKResponse(createResponse, null, {
7863
7977
  preferResponseOnMissingData: true
@@ -8355,8 +8469,8 @@ function extractToolRows(parts) {
8355
8469
  const record = part;
8356
8470
  if (record.type !== "tool" || typeof record.tool !== "string")
8357
8471
  continue;
8358
- const state = record.state;
8359
- const stateRecord = state && typeof state === "object" ? state : {};
8472
+ const state2 = record.state;
8473
+ const stateRecord = state2 && typeof state2 === "object" ? state2 : {};
8360
8474
  const output = stringifyToolOutput(stateRecord.output);
8361
8475
  const errorText = stringifyToolOutput(stateRecord.error);
8362
8476
  const status = typeof stateRecord.status === "string" ? stateRecord.status : "";
@@ -8473,7 +8587,7 @@ function recordRetrospectiveWindowProcessed(db, projectPath, windowKey) {
8473
8587
  }
8474
8588
 
8475
8589
  // ../plugin/src/features/magic-context/dreamer/verify.ts
8476
- import { createHash as createHash6 } from "node:crypto";
8590
+ import { createHash as createHash5 } from "node:crypto";
8477
8591
 
8478
8592
  // ../plugin/src/features/magic-context/dreamer/verify-gate.ts
8479
8593
  import path5 from "node:path";
@@ -8683,12 +8797,12 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
8683
8797
  let agentSessionId = null;
8684
8798
  const startedAt = Date.now();
8685
8799
  try {
8686
- const createResponse = await args.client.session.create({
8687
- body: {
8688
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
8689
- title: "magic-context-dream-verify"
8690
- },
8691
- query: { directory: args.sessionDirectory }
8800
+ const createResponse = await createChildSessionWithFence({
8801
+ client: args.client,
8802
+ db: args.db,
8803
+ parentSessionId: args.parentSessionId,
8804
+ title: "magic-context-dream-verify",
8805
+ directory: args.sessionDirectory
8692
8806
  });
8693
8807
  const created = normalizeSDKResponse(createResponse, null, {
8694
8808
  preferResponseOnMissingData: true
@@ -8811,7 +8925,7 @@ async function applyVerifyManifest(args, batch, manifestText) {
8811
8925
  memory_project: args.projectIdentity,
8812
8926
  context_store_uuid: args.moduleRoute.moduleContextStoreUuid,
8813
8927
  authority_generation: args.moduleRoute.moduleAuthorityGeneration,
8814
- command_id: `${args.moduleRoute.moduleCommandId}:${createHash6("sha256").update(rows.map((row) => row.memory_id).join(",")).digest("hex").slice(0, 16)}`,
8928
+ command_id: `${args.moduleRoute.moduleCommandId}:${createHash5("sha256").update(rows.map((row) => row.memory_id).join(",")).digest("hex").slice(0, 16)}`,
8815
8929
  rows
8816
8930
  }
8817
8931
  }
@@ -9308,7 +9422,7 @@ function parseFrictionGateVerdict(verdict) {
9308
9422
  }
9309
9423
  function computeRetrospectiveWindowKey(flagged) {
9310
9424
  const anchors = flagged.map((message) => `${message.sessionId}:${message.ts}`).sort().join("|");
9311
- return createHash7("sha256").update(anchors).digest("hex").slice(0, 32);
9425
+ return createHash6("sha256").update(anchors).digest("hex").slice(0, 32);
9312
9426
  }
9313
9427
  function renderFrictionWindow(messages, flaggedOrdinals, radius = 2) {
9314
9428
  const flagged = new Set(flaggedOrdinals);
@@ -9375,12 +9489,12 @@ async function runRetrospectiveTask(config, ctx, helpers) {
9375
9489
  });
9376
9490
  let childSessionId = null;
9377
9491
  try {
9378
- const createResponse = await deps.client.session.create({
9379
- body: {
9380
- ...parent ? { parentID: parent } : {},
9381
- title: "magic-context-dream-retrospective"
9382
- },
9383
- query: { directory: deps.sessionDirectory }
9492
+ const createResponse = await createChildSessionWithFence({
9493
+ client: deps.client,
9494
+ db,
9495
+ parentSessionId: parent ?? undefined,
9496
+ title: "magic-context-dream-retrospective",
9497
+ directory: deps.sessionDirectory
9384
9498
  });
9385
9499
  const created = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
9386
9500
  childSessionId = typeof created?.id === "string" ? created.id : null;
@@ -9541,8 +9655,8 @@ async function runAgenticTask(config, ctx, helpers) {
9541
9655
  const lastRunAt = getTaskScheduleState(db, projectIdentity, config.task)?.lastRunAt ?? null;
9542
9656
  const maintainDocsSnapshot = task === "maintain-docs" ? snapshotMaintainDocsFiles(docsDir) : undefined;
9543
9657
  const existingDocs = task === "maintain-docs" ? {
9544
- architecture: existsSync8(`${docsDir}/ARCHITECTURE.md`),
9545
- structure: existsSync8(`${docsDir}/STRUCTURE.md`)
9658
+ architecture: existsSync7(`${docsDir}/ARCHITECTURE.md`),
9659
+ structure: existsSync7(`${docsDir}/STRUCTURE.md`)
9546
9660
  } : undefined;
9547
9661
  const userMemories = task === "curate" ? getActiveUserMemories(db).map((um) => ({ id: um.id, content: um.content })) : undefined;
9548
9662
  let curateMemories;
@@ -9565,12 +9679,12 @@ async function runAgenticTask(config, ctx, helpers) {
9565
9679
  });
9566
9680
  let childSessionId = null;
9567
9681
  try {
9568
- const createResponse = await deps.client.session.create({
9569
- body: {
9570
- ...parent ? { parentID: parent } : {},
9571
- title: `magic-context-dream-${task}`
9572
- },
9573
- query: { directory: docsDir }
9682
+ const createResponse = await createChildSessionWithFence({
9683
+ client: deps.client,
9684
+ db,
9685
+ parentSessionId: parent ?? undefined,
9686
+ title: `magic-context-dream-${task}`,
9687
+ directory: docsDir
9574
9688
  });
9575
9689
  const created = normalizeSDKResponse(createResponse, null, {
9576
9690
  preferResponseOnMissingData: true
@@ -9742,11 +9856,11 @@ function planDueTasks(db, projectIdentity, tasks, now) {
9742
9856
  const due = [];
9743
9857
  for (const config of tasks) {
9744
9858
  reconcileSchedule(db, projectIdentity, config, now);
9745
- const state = getTaskScheduleState(db, projectIdentity, config.task);
9746
- if (!state || state.nextDueAt === null)
9859
+ const state2 = getTaskScheduleState(db, projectIdentity, config.task);
9860
+ if (!state2 || state2.nextDueAt === null)
9747
9861
  continue;
9748
- if (now >= state.nextDueAt) {
9749
- due.push({ config, scheduledAt: state.nextDueAt });
9862
+ if (now >= state2.nextDueAt) {
9863
+ due.push({ config, scheduledAt: state2.nextDueAt });
9750
9864
  }
9751
9865
  }
9752
9866
  return due;
@@ -11251,6 +11365,101 @@ function executeFlush(db, sessionId) {
11251
11365
  }
11252
11366
  }
11253
11367
 
11368
+ // src/compaction-off-pi.ts
11369
+ var COMPACTION_OFF_COMMAND_UNAVAILABLE = `Unavailable: magic-context is in compaction-off mode (${COMPACTION_ENABLED_PATH}=false).`;
11370
+ var NO_TRANSITION = {
11371
+ recordToWrite: null,
11372
+ invalidatedBaseline: false,
11373
+ clearDeferredMarkerState: false,
11374
+ historianCatchUpSignaled: false,
11375
+ notice: null
11376
+ };
11377
+ var PI_COMPACTION_OFF_NOTICE = "Magic Context is in compaction-off mode. Native Pi compaction now owns this session's context window; the first turn after disabling may trigger one native compaction cycle on long sessions.";
11378
+ var PI_COMPACTION_ON_NOTICE = "Magic Context context-window management resumed. Run /ctx-wrapup to digest history accumulated while compaction was off.";
11379
+ function reconcilePiCompactionMode(args) {
11380
+ const stored = getCompactionModeRecord(args.db, args.sessionId);
11381
+ if (stored === "on_notice_pending") {
11382
+ return {
11383
+ ...NO_TRANSITION,
11384
+ recordToWrite: "on",
11385
+ notice: PI_COMPACTION_ON_NOTICE
11386
+ };
11387
+ }
11388
+ if (stored === "off_notice_pending") {
11389
+ return {
11390
+ ...NO_TRANSITION,
11391
+ recordToWrite: "off",
11392
+ notice: PI_COMPACTION_OFF_NOTICE
11393
+ };
11394
+ }
11395
+ if (!args.compactionOff) {
11396
+ if (stored === null || resolveCompactionModeRecord(stored) === "on") {
11397
+ return stored === null ? { ...NO_TRANSITION, recordToWrite: "on" } : NO_TRANSITION;
11398
+ }
11399
+ clearCachedM0M1(args.db, args.sessionId);
11400
+ if (!args.historianRunnable) {
11401
+ return {
11402
+ ...NO_TRANSITION,
11403
+ recordToWrite: "on",
11404
+ invalidatedBaseline: true
11405
+ };
11406
+ }
11407
+ updateSessionMeta(args.db, args.sessionId, {
11408
+ compartmentInProgress: true
11409
+ });
11410
+ setCompactionModeRecord(args.db, args.sessionId, "on_notice_pending");
11411
+ return {
11412
+ recordToWrite: "on",
11413
+ invalidatedBaseline: true,
11414
+ clearDeferredMarkerState: false,
11415
+ historianCatchUpSignaled: true,
11416
+ notice: PI_COMPACTION_ON_NOTICE
11417
+ };
11418
+ }
11419
+ if (resolveCompactionModeRecord(stored) === "off")
11420
+ return NO_TRANSITION;
11421
+ let clearedSomething = false;
11422
+ if (getPendingPiCompactionMarkerState(args.db, args.sessionId) !== null) {
11423
+ setPendingPiCompactionMarkerState(args.db, args.sessionId, null);
11424
+ clearedSomething = true;
11425
+ }
11426
+ if (getOverflowState(args.db, args.sessionId).needsEmergencyRecovery) {
11427
+ clearEmergencyRecovery(args.db, args.sessionId);
11428
+ clearedSomething = true;
11429
+ }
11430
+ const channel2 = getChannel2NudgeState(args.db, args.sessionId);
11431
+ if (channel2 === "pending" || channel2 === "claimed") {
11432
+ setChannel2NudgeState(args.db, args.sessionId, "");
11433
+ clearedSomething = true;
11434
+ }
11435
+ if (getPendingOps(args.db, args.sessionId).length > 0) {
11436
+ clearPendingOps(args.db, args.sessionId);
11437
+ clearedSomething = true;
11438
+ }
11439
+ const meta = getOrCreateSessionMeta(args.db, args.sessionId);
11440
+ if (meta.compartmentInProgress) {
11441
+ updateSessionMeta(args.db, args.sessionId, {
11442
+ compartmentInProgress: false
11443
+ });
11444
+ }
11445
+ clearCachedM0M1(args.db, args.sessionId);
11446
+ sessionLog(args.sessionId, `Pi compaction-off transition: clearedPendingMarker=${clearedSomething}`);
11447
+ const notice = clearedSomething ? PI_COMPACTION_OFF_NOTICE : null;
11448
+ if (notice) {
11449
+ setCompactionModeRecord(args.db, args.sessionId, "off_notice_pending");
11450
+ }
11451
+ return {
11452
+ recordToWrite: "off",
11453
+ invalidatedBaseline: true,
11454
+ clearDeferredMarkerState: true,
11455
+ historianCatchUpSignaled: false,
11456
+ notice
11457
+ };
11458
+ }
11459
+ function commitPiCompactionModeRecord(db, sessionId, record) {
11460
+ setCompactionModeRecord(db, sessionId, record);
11461
+ }
11462
+
11254
11463
  // src/context-handler.ts
11255
11464
  import * as crypto3 from "node:crypto";
11256
11465
 
@@ -11264,6 +11473,9 @@ var UNIT_TO_MS = {
11264
11473
  };
11265
11474
  function parseCacheTtl(ttl) {
11266
11475
  const normalizedTtl = ttl.trim();
11476
+ if (normalizedTtl.toLowerCase() === "never") {
11477
+ return Number.POSITIVE_INFINITY;
11478
+ }
11267
11479
  if (NUMERIC_PATTERN.test(normalizedTtl)) {
11268
11480
  return Number(normalizedTtl);
11269
11481
  }
@@ -11979,9 +12191,9 @@ function applyFlushedStatuses(sessionId, db, targets, preloadedTags) {
11979
12191
  }
11980
12192
 
11981
12193
  // ../plugin/src/hooks/magic-context/boundary-execution.ts
11982
- var FORCE_MATERIALIZE_PERCENTAGE = 85;
11983
12194
  function detectMidTurnBypassReason(input) {
11984
- if (input.contextUsage.percentage >= FORCE_MATERIALIZE_PERCENTAGE)
12195
+ const { forceMaterializationPercentage } = escalationBands(input.effectiveExecuteThresholdPercentage);
12196
+ if (input.contextUsage.percentage >= forceMaterializationPercentage)
11985
12197
  return "force-materialize";
11986
12198
  if (input.historyRefreshSessions.has(input.sessionId))
11987
12199
  return "explicit-bust";
@@ -12474,29 +12686,29 @@ function callIdFromPart(part) {
12474
12686
  const direct = firstStringField(part, ["callID", "callId", "toolCallId", "tool_call_id", "id"]);
12475
12687
  if (direct)
12476
12688
  return direct;
12477
- const state = isRecord(part.state) ? part.state : null;
12478
- return state ? firstStringField(state, ["callID", "callId", "toolCallId", "tool_call_id", "id"]) ?? "" : "";
12689
+ const state2 = isRecord(part.state) ? part.state : null;
12690
+ return state2 ? firstStringField(state2, ["callID", "callId", "toolCallId", "tool_call_id", "id"]) ?? "" : "";
12479
12691
  }
12480
12692
  function toolSignalFromPart(part) {
12481
12693
  if (!isRecord(part))
12482
12694
  return null;
12483
12695
  const type = partType(part);
12484
- const state = isRecord(part.state) ? part.state : null;
12696
+ const state2 = isRecord(part.state) ? part.state : null;
12485
12697
  const callId = callIdFromPart(part);
12486
12698
  if (!callId && type !== "tool")
12487
12699
  return null;
12488
12700
  if (type === "tool") {
12489
- const hasInput = state !== null && hasOwn(state, "input");
12490
- const outputKey = state ? hasOwn(state, "output") ? "output" : hasOwn(state, "error") ? "error" : hasOwn(state, "result") ? "result" : null : null;
12701
+ const hasInput = state2 !== null && hasOwn(state2, "input");
12702
+ const outputKey = state2 ? hasOwn(state2, "output") ? "output" : hasOwn(state2, "error") ? "error" : hasOwn(state2, "result") ? "result" : null : null;
12491
12703
  const hasOutput = outputKey !== null;
12492
- const outputValue = outputKey && state ? state[outputKey] : undefined;
12704
+ const outputValue = outputKey && state2 ? state2[outputKey] : undefined;
12493
12705
  const providerExecuted = part.providerExecuted === true;
12494
12706
  const openInvocation = !providerExecuted && !hasOutput;
12495
12707
  return {
12496
12708
  callId,
12497
12709
  hasInput: hasInput || openInvocation,
12498
12710
  hasOutput,
12499
- inputText: hasInput && state ? stringValue(state.input) : "",
12711
+ inputText: hasInput && state2 ? stringValue(state2.input) : "",
12500
12712
  outputText: hasOutput ? stringValue(outputValue) : ""
12501
12713
  };
12502
12714
  }
@@ -12724,24 +12936,27 @@ function tokenForMessage(message, options) {
12724
12936
  function buildTrueRawTokenIndex(sessionId, messages, options) {
12725
12937
  const ordered = [...messages].sort((a, b) => a.ordinal - b.ordinal);
12726
12938
  const sliceCount = ordered.length;
12727
- const maxOrdinal = ordered.length > 0 ? ordered[ordered.length - 1].ordinal : 0;
12728
- const rawMessageCount = Math.max(sliceCount, maxOrdinal, options.absoluteMessageCount ?? sliceCount);
12939
+ const firstOrdinal = ordered.length > 0 ? ordered[0].ordinal : 1;
12940
+ const terminalOrdinal = ordered.length > 0 ? ordered[ordered.length - 1].ordinal : 0;
12941
+ const rawMessageCount = Math.max(sliceCount, terminalOrdinal, options.absoluteMessageCount ?? sliceCount);
12942
+ const ordinalSpan = terminalOrdinal >= firstOrdinal ? terminalOrdinal - firstOrdinal + 1 : 0;
12729
12943
  const tokensByOrdinal = new Map;
12730
12944
  const idsByOrdinal = new Map;
12731
- const prefix = new Array(rawMessageCount + 1).fill(0);
12945
+ const prefix = new Array(ordinalSpan + 1).fill(0);
12732
12946
  for (const message of ordered) {
12733
12947
  const stored = options.storedTotalForMessage?.(message);
12734
12948
  const total = stored !== undefined && stored !== null ? stored : tokenForMessage(message, options).total;
12735
12949
  tokensByOrdinal.set(message.ordinal, total);
12736
12950
  idsByOrdinal.set(message.ordinal, message.id);
12737
- if (message.ordinal >= 1 && message.ordinal <= rawMessageCount) {
12738
- prefix[message.ordinal] = total;
12951
+ const relative = message.ordinal - firstOrdinal + 1;
12952
+ if (relative >= 1 && relative <= ordinalSpan) {
12953
+ prefix[relative] = total;
12739
12954
  }
12740
12955
  }
12741
- for (let k = 1;k <= rawMessageCount; k += 1) {
12956
+ for (let k = 1;k <= ordinalSpan; k += 1) {
12742
12957
  prefix[k] += prefix[k - 1];
12743
12958
  }
12744
- const ordinalToIndex = (ordinal) => Math.max(0, Math.min(rawMessageCount, ordinal - 1));
12959
+ const ordinalToIndex = (ordinal) => Math.max(0, Math.min(ordinalSpan, ordinal - firstOrdinal));
12745
12960
  return {
12746
12961
  sessionId,
12747
12962
  providerShapeVersion: options.providerShapeVersion,
@@ -12753,27 +12968,27 @@ function buildTrueRawTokenIndex(sessionId, messages, options) {
12753
12968
  return idsByOrdinal.get(ordinal) ?? null;
12754
12969
  },
12755
12970
  suffixTokensFromOrdinal(ordinal) {
12756
- if (ordinal <= 1)
12757
- return prefix[rawMessageCount];
12758
- if (ordinal > rawMessageCount)
12971
+ if (ordinal <= firstOrdinal)
12972
+ return prefix[ordinalSpan];
12973
+ if (ordinal > terminalOrdinal)
12759
12974
  return 0;
12760
- return prefix[rawMessageCount] - prefix[ordinalToIndex(ordinal)];
12975
+ return prefix[ordinalSpan] - prefix[ordinalToIndex(ordinal)];
12761
12976
  },
12762
12977
  rangeTokens(startInclusive, endExclusive) {
12763
- const start = Math.max(1, startInclusive);
12764
- const end = Math.max(start, Math.min(rawMessageCount + 1, endExclusive));
12765
- return prefix[end - 1] - prefix[start - 1];
12978
+ const start = Math.max(firstOrdinal, startInclusive);
12979
+ const end = Math.max(start, Math.min(terminalOrdinal + 1, endExclusive));
12980
+ return prefix[end - firstOrdinal] - prefix[start - firstOrdinal];
12766
12981
  },
12767
12982
  findSuffixStartForTokens(tokens) {
12768
12983
  if (!Number.isFinite(tokens) || tokens <= 0)
12769
- return rawMessageCount + 1;
12984
+ return terminalOrdinal + 1;
12770
12985
  const target = Math.max(0, Math.floor(tokens));
12771
- const total = prefix[rawMessageCount];
12986
+ const total = prefix[ordinalSpan];
12772
12987
  if (total < target)
12773
- return 1;
12988
+ return firstOrdinal;
12774
12989
  const cut = total - target;
12775
12990
  let lo = 0;
12776
- let hi = rawMessageCount;
12991
+ let hi = ordinalSpan;
12777
12992
  let best = 0;
12778
12993
  while (lo <= hi) {
12779
12994
  const mid = lo + hi >> 1;
@@ -12784,29 +12999,31 @@ function buildTrueRawTokenIndex(sessionId, messages, options) {
12784
12999
  hi = mid - 1;
12785
13000
  }
12786
13001
  }
12787
- return best + 1;
13002
+ return firstOrdinal + best;
12788
13003
  },
12789
13004
  findHeadEndForCap(startInclusive, endExclusive, capTokens) {
12790
- const start = Math.max(1, Math.min(rawMessageCount + 1, startInclusive));
12791
- const end = Math.max(start, Math.min(rawMessageCount + 1, endExclusive));
13005
+ const start = Math.max(firstOrdinal, Math.min(terminalOrdinal + 1, startInclusive));
13006
+ const end = Math.max(start, Math.min(terminalOrdinal + 1, endExclusive));
12792
13007
  if (!Number.isFinite(capTokens) || capTokens <= 0)
12793
13008
  return start;
12794
- const startPrefix = prefix[start - 1];
12795
- const cut = startPrefix + Math.floor(capTokens);
12796
- let lo = start;
12797
- let hi = end - 1;
12798
- let bestEnd = start;
13009
+ const startIndex = start - firstOrdinal;
13010
+ const endIndex = end - firstOrdinal;
13011
+ const cut = prefix[startIndex] + Math.floor(capTokens);
13012
+ let lo = startIndex + 1;
13013
+ let hi = endIndex;
13014
+ let bestEndIndex = startIndex;
12799
13015
  while (lo <= hi) {
12800
13016
  const mid = lo + hi >> 1;
12801
13017
  if (prefix[mid] <= cut) {
12802
- bestEnd = mid + 1;
13018
+ bestEndIndex = mid;
12803
13019
  lo = mid + 1;
12804
13020
  } else {
12805
13021
  hi = mid - 1;
12806
13022
  }
12807
13023
  }
13024
+ let bestEnd = firstOrdinal + bestEndIndex;
12808
13025
  if (bestEnd === start && start < end)
12809
- return start + 1;
13026
+ bestEnd = start + 1;
12810
13027
  return Math.min(bestEnd, end);
12811
13028
  }
12812
13029
  };
@@ -13122,7 +13339,8 @@ function resolveProtectedTailBoundary(ctx) {
13122
13339
  if (ctx.migrationFloorActive)
13123
13340
  runtimeFloor = Math.max(runtimeFloor, ctx.priorBoundaryOrdinal);
13124
13341
  let protectedTailStart = Math.max(boundary, runtimeFloor);
13125
- if (!ctx.emergencyTailScale && usagePercentage < 80) {
13342
+ const forceMaterializationPercentage = escalationBands(ctx.executeThresholdPercentage).forceMaterializationPercentage;
13343
+ if (!ctx.emergencyTailScale && usagePercentage < forceMaterializationPercentage) {
13126
13344
  let lastMeaningfulUserOrdinal = 0;
13127
13345
  for (let i = messages.length - 1;i >= 0; i--) {
13128
13346
  const message = messages[i];
@@ -13363,7 +13581,8 @@ function getRawHistoryEligibility(db, sessionId) {
13363
13581
  function hasRunnableCompartmentWindow(snapshot) {
13364
13582
  if (snapshot.offset >= snapshot.protectedTailStart)
13365
13583
  return false;
13366
- if (snapshot.usagePercentage >= 80 || snapshot.emergencyTailScale) {
13584
+ const forceMaterializationPercentage = escalationBands(snapshot.executeThresholdPercentage).forceMaterializationPercentage;
13585
+ if (snapshot.usagePercentage >= forceMaterializationPercentage || snapshot.emergencyTailScale) {
13367
13586
  return snapshot.trueRawEligibleTokens >= deriveMinForceEligibleTokens(snapshot.N) || snapshot.eligibleEndOrdinal > snapshot.offset;
13368
13587
  }
13369
13588
  return snapshot.eligibleEndOrdinal > snapshot.offset;
@@ -13426,11 +13645,18 @@ function validateBoundarySnapshot(args) {
13426
13645
  return { ok: true };
13427
13646
  }
13428
13647
  function recordHighPressureNoEligibleHead(db, snapshot) {
13429
- if (snapshot.usagePercentage < 80 && !snapshot.emergencyTailScale)
13648
+ const forceMaterializationPercentage = escalationBands(snapshot.executeThresholdPercentage).forceMaterializationPercentage;
13649
+ if (snapshot.usagePercentage < forceMaterializationPercentage && !snapshot.emergencyTailScale) {
13430
13650
  return 0;
13651
+ }
13431
13652
  return recordProtectedTailNoEligibleHead(db, snapshot.sessionId);
13432
13653
  }
13433
13654
 
13655
+ // ../plugin/src/hooks/magic-context/sentinel.ts
13656
+ function modelAcceptsEmptyContent(providerID) {
13657
+ return providerID === "anthropic";
13658
+ }
13659
+
13434
13660
  // ../plugin/src/hooks/magic-context/compartment-trigger.ts
13435
13661
  var PROACTIVE_TRIGGER_OFFSET_PERCENTAGE = 2;
13436
13662
  var POST_DROP_TARGET_RATIO = 0.75;
@@ -13438,7 +13664,6 @@ var MIN_PROACTIVE_TAIL_TOKEN_ESTIMATE = 6000;
13438
13664
  var MIN_PROACTIVE_TAIL_MESSAGE_COUNT = 12;
13439
13665
  var DEFAULT_MIN_COMMIT_CLUSTERS_FOR_TRIGGER = 3;
13440
13666
  var TAIL_SIZE_TRIGGER_MULTIPLIER = 3;
13441
- var FORCE_COMPARTMENT_PERCENTAGE = 80;
13442
13667
  var BLOCK_UNTIL_DONE_PERCENTAGE = 95;
13443
13668
  var CONTENT_TAG_OWNER_SUFFIX = /:(?:p|file)\d+$/;
13444
13669
  function tagOwnerMessageId(row) {
@@ -13475,7 +13700,7 @@ function estimateUntaggedInMemoryTailUpperBound(db, sessionId, inMemoryTail, tag
13475
13700
  function getProactiveCompartmentTriggerPercentage(executeThresholdPercentage) {
13476
13701
  return Math.max(0, executeThresholdPercentage - PROACTIVE_TRIGGER_OFFSET_PERCENTAGE);
13477
13702
  }
13478
- function estimateProjectedPostDropPercentage(db, sessionId, usage, activeTags, clearReasoningAge, clearedReasoningThroughTag) {
13703
+ function estimateProjectedPostDropPercentage(db, sessionId, usage, activeTags, clearReasoningAge, clearedReasoningThroughTag, canClearReasoning) {
13479
13704
  const totalActiveBytes = activeTags.reduce((sum, tag) => sum + tag.byteSize + tag.reasoningByteSize, 0);
13480
13705
  if (totalActiveBytes === 0)
13481
13706
  return null;
@@ -13486,7 +13711,7 @@ function estimateProjectedPostDropPercentage(db, sessionId, usage, activeTags, c
13486
13711
  droppableBytes += activeTags.filter((tag) => pendingDropTagIds.has(tag.tagNumber)).reduce((sum, tag) => sum + tag.byteSize + tag.reasoningByteSize, 0);
13487
13712
  }
13488
13713
  const maxTag = activeTags.reduce((max, t) => Math.max(max, t.tagNumber), 0);
13489
- if (clearReasoningAge !== undefined && clearedReasoningThroughTag !== undefined) {
13714
+ if (canClearReasoning && clearReasoningAge !== undefined && clearedReasoningThroughTag !== undefined) {
13490
13715
  const reasoningAgeCutoff = maxTag - clearReasoningAge;
13491
13716
  for (const tag of activeTags) {
13492
13717
  if (tag.type !== "message")
@@ -13586,7 +13811,7 @@ function getUnsummarizedTailInfo(db, sessionId, triggerBudget, usage, executeThr
13586
13811
  }
13587
13812
  });
13588
13813
  }
13589
- function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPercentage, executeThresholdPercentage, triggerBudget, clearReasoningAge, commitClusterTrigger, preloadedActiveTags, contextLimit, inMemoryTail, taggerFloorOverride) {
13814
+ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPercentage, executeThresholdPercentage, triggerBudget, clearReasoningAge, commitClusterTrigger, preloadedActiveTags, contextLimit, inMemoryTail, taggerFloorOverride, reasoningProjection) {
13590
13815
  if (sessionMeta.compartmentInProgress) {
13591
13816
  sessionLog(sessionId, `compartment trigger: skipped — historian already in progress (usage=${usage.percentage.toFixed(1)}%)`);
13592
13817
  return { shouldFire: false };
@@ -13640,18 +13865,20 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
13640
13865
  }
13641
13866
  return { shouldFire: false };
13642
13867
  }
13643
- const projectedPostDropPercentage = estimateProjectedPostDropPercentage(db, sessionId, usage, preloadedActiveTags ?? getActiveTagsBySession(db, sessionId), clearReasoningAge, sessionMeta.clearedReasoningThroughTag);
13868
+ const canClearReasoning = reasoningProjection?.canClearReasoning ?? modelAcceptsEmptyContent(reasoningProjection?.providerID);
13869
+ const projectedPostDropPercentage = estimateProjectedPostDropPercentage(db, sessionId, usage, preloadedActiveTags ?? getActiveTagsBySession(db, sessionId), clearReasoningAge, sessionMeta.clearedReasoningThroughTag, canClearReasoning);
13644
13870
  const relativePostDropTarget = executeThresholdPercentage * POST_DROP_TARGET_RATIO;
13645
- if (usage.percentage >= FORCE_COMPARTMENT_PERCENTAGE) {
13871
+ const forceMaterializationPercentage = escalationBands(executeThresholdPercentage).forceMaterializationPercentage;
13872
+ if (usage.percentage >= forceMaterializationPercentage) {
13646
13873
  if (projectedPostDropPercentage !== null && projectedPostDropPercentage <= relativePostDropTarget) {
13647
- sessionLog(sessionId, `compartment trigger: skipping force-${FORCE_COMPARTMENT_PERCENTAGE} because projected post-drop usage is ${projectedPostDropPercentage.toFixed(1)}% (target ${relativePostDropTarget.toFixed(1)}%)`);
13874
+ sessionLog(sessionId, `compartment trigger: skipping force band ${forceMaterializationPercentage}% because projected post-drop usage is ${projectedPostDropPercentage.toFixed(1)}% (target ${relativePostDropTarget.toFixed(1)}%)`);
13648
13875
  return { shouldFire: false };
13649
13876
  }
13650
13877
  sessionLog(sessionId, `compartment trigger: force-firing at ${usage.percentage.toFixed(1)}% (projected post-drop ${projectedPostDropPercentage?.toFixed(1) ?? "none"}%)`);
13651
13878
  if (tailInfo.boundarySnapshot && hasRunnableCompartmentWindow(tailInfo.boundarySnapshot)) {
13652
13879
  return {
13653
13880
  shouldFire: true,
13654
- reason: "force_80",
13881
+ reason: "force_band",
13655
13882
  boundarySnapshot: tailInfo.boundarySnapshot
13656
13883
  };
13657
13884
  }
@@ -13676,9 +13903,9 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
13676
13903
  });
13677
13904
  });
13678
13905
  if (hasRunnableCompartmentWindow(scaledBoundary)) {
13679
- return { shouldFire: true, reason: "force_80", boundarySnapshot: scaledBoundary };
13906
+ return { shouldFire: true, reason: "force_band", boundarySnapshot: scaledBoundary };
13680
13907
  }
13681
- sessionLog(sessionId, "compartment trigger: force_80 skipped — raw exists but protected head genuinely empty after emergency tail scale");
13908
+ sessionLog(sessionId, "compartment trigger: force_band skipped — raw exists but protected head genuinely empty after emergency tail scale");
13682
13909
  return { shouldFire: false };
13683
13910
  }
13684
13911
  const clusterEnabled = commitClusterTrigger?.enabled ?? true;
@@ -13843,11 +14070,6 @@ ${body}${hintText}
13843
14070
  </system-reminder>`;
13844
14071
  }
13845
14072
 
13846
- // ../plugin/src/hooks/magic-context/sentinel.ts
13847
- function modelAcceptsEmptyContent(providerID) {
13848
- return providerID === "anthropic";
13849
- }
13850
-
13851
14073
  // ../plugin/src/hooks/magic-context/supersession-reclaim.ts
13852
14074
  var TODOWRITE_KEEP = 1;
13853
14075
  var CTX_REDUCE_KEEP = 5;
@@ -13977,7 +14199,7 @@ function advanceToolReclaimWatermarkToCurrentMax(db, sessionId) {
13977
14199
  }
13978
14200
 
13979
14201
  // ../plugin/src/shared/tag-transcript.ts
13980
- import { createHash as createHash8 } from "node:crypto";
14202
+ import { createHash as createHash7 } from "node:crypto";
13981
14203
 
13982
14204
  // ../plugin/src/hooks/magic-context/image-token-estimate.ts
13983
14205
  var IMAGE_TOKEN_DIVISOR = 750;
@@ -14132,7 +14354,7 @@ function readUint32BE(b, offset) {
14132
14354
  // ../plugin/src/shared/tag-transcript.ts
14133
14355
  var TEXT_TAG_IDENTITY_MARKER = ":mc-text-v1:";
14134
14356
  function textIdentityDigest(value) {
14135
- return createHash8("sha256").update(value).digest("hex");
14357
+ return createHash7("sha256").update(value).digest("hex");
14136
14358
  }
14137
14359
  function buildContentDerivedTextIds(messageId, parts) {
14138
14360
  const sources = parts.filter((part) => part.kind === "text").map((part) => stripTagPrefix(part.getText() ?? ""));
@@ -15162,16 +15384,16 @@ function sealDeliveredAfterUnconfirmedSend(db, sessionId) {
15162
15384
  }
15163
15385
  }
15164
15386
  var channel1StateBySession = new Map;
15165
- function setPiChannel1Baseline(sessionId, state) {
15166
- channel1StateBySession.set(sessionId, state);
15387
+ function setPiChannel1Baseline(sessionId, state2) {
15388
+ channel1StateBySession.set(sessionId, state2);
15167
15389
  }
15168
15390
  function clearPiChannel1State(sessionId) {
15169
15391
  channel1StateBySession.delete(sessionId);
15170
15392
  }
15171
15393
  function markPiChannel1Reduced(sessionId, db) {
15172
- const state = channel1StateBySession.get(sessionId);
15173
- if (state)
15174
- state.reducedSinceRefresh = true;
15394
+ const state2 = channel1StateBySession.get(sessionId);
15395
+ if (state2)
15396
+ state2.reducedSinceRefresh = true;
15175
15397
  if (db)
15176
15398
  resetLastNudgeCycle(db, sessionId);
15177
15399
  }
@@ -15246,11 +15468,11 @@ function computeTailTokenEstimatePi(messages) {
15246
15468
  }
15247
15469
  function maybeChannel1ReminderForToolResult(args) {
15248
15470
  const { db, sessionId, toolName } = args;
15249
- const state = channel1StateBySession.get(sessionId);
15250
- if (!state)
15471
+ const state2 = channel1StateBySession.get(sessionId);
15472
+ if (!state2)
15251
15473
  return null;
15252
15474
  if (toolName === "ctx_reduce") {
15253
- state.reducedSinceRefresh = true;
15475
+ state2.reducedSinceRefresh = true;
15254
15476
  resetLastNudgeCycle(db, sessionId);
15255
15477
  return null;
15256
15478
  }
@@ -15259,21 +15481,21 @@ function maybeChannel1ReminderForToolResult(args) {
15259
15481
  return null;
15260
15482
  if (text.includes(CHANNEL1_SENTINEL))
15261
15483
  return null;
15262
- state.turnToolTokens += toolOutputTokens(text);
15263
- if (state.reducedSinceRefresh)
15484
+ state2.turnToolTokens += toolOutputTokens(text);
15485
+ if (state2.reducedSinceRefresh)
15264
15486
  return null;
15265
- const undroppedTokens = state.tailToolTokens + state.turnToolTokens;
15487
+ const undroppedTokens = state2.tailToolTokens + state2.turnToolTokens;
15266
15488
  const pressure = computePressure({
15267
- lastInputTokens: state.lastInputTokens,
15268
- turnToolTokens: state.turnToolTokens,
15269
- contextLimit: state.contextLimit,
15270
- executeThresholdPercentage: state.executeThresholdPercentage
15489
+ lastInputTokens: state2.lastInputTokens,
15490
+ turnToolTokens: state2.turnToolTokens,
15491
+ contextLimit: state2.contextLimit,
15492
+ executeThresholdPercentage: state2.executeThresholdPercentage
15271
15493
  });
15272
- const workingWindowTokens = Math.round(state.contextLimit * state.executeThresholdPercentage / 100);
15494
+ const workingWindowTokens = Math.round(state2.contextLimit * state2.executeThresholdPercentage / 100);
15273
15495
  const decision = decideChannel1({
15274
15496
  undroppedTokens,
15275
15497
  pressure,
15276
- estimatedInputTokens: state.lastInputTokens + state.turnToolTokens,
15498
+ estimatedInputTokens: state2.lastInputTokens + state2.turnToolTokens,
15277
15499
  workingWindowTokens,
15278
15500
  lastNudgeUndropped: getLastNudgeUndropped(db, sessionId),
15279
15501
  lastNudgeLevel: getLastNudgeLevel(db, sessionId),
@@ -15285,18 +15507,18 @@ function maybeChannel1ReminderForToolResult(args) {
15285
15507
  return null;
15286
15508
  return {
15287
15509
  type: "text",
15288
- text: buildChannel1Reminder(decision.level, decision.undroppedTokens, state.oldestReclaimableToolTags)
15510
+ text: buildChannel1Reminder(decision.level, decision.undroppedTokens, state2.oldestReclaimableToolTags)
15289
15511
  };
15290
15512
  }
15291
15513
  var CHANNEL2_NUDGE_CUSTOM_TYPE = "magic-context:ceiling-nudge";
15292
15514
  function maybeDeliverChannel2Pi(pi, db, sessionId, deliverAs = "followUp") {
15293
- let state;
15515
+ let state2;
15294
15516
  try {
15295
- state = getChannel2NudgeState(db, sessionId);
15517
+ state2 = getChannel2NudgeState(db, sessionId);
15296
15518
  } catch {
15297
15519
  return false;
15298
15520
  }
15299
- if (state !== "pending")
15521
+ if (state2 !== "pending")
15300
15522
  return false;
15301
15523
  const baseline = channel1StateBySession.get(sessionId);
15302
15524
  if (!baseline)
@@ -15968,14 +16190,17 @@ var EMPTY_PI_PROJECT_DOCS = {
15968
16190
  renderedBlock: "",
15969
16191
  canonicalHash: ""
15970
16192
  };
15971
- function readProjectDocsForPiM0(state) {
15972
- return state.injectDocs !== false ? readProjectDocsCanonical(state.projectDirectory) : EMPTY_PI_PROJECT_DOCS;
16193
+ function getRenderableCompartmentsPi(db, state2) {
16194
+ return state2.compactionOff ? [] : getCompartments(db, state2.sessionId);
16195
+ }
16196
+ function readProjectDocsForPiM0(state2) {
16197
+ return state2.injectDocs !== false ? readProjectDocsCanonical(state2.projectDirectory) : EMPTY_PI_PROJECT_DOCS;
15973
16198
  }
15974
- function memoryProjectPath(state) {
15975
- return state.memoryEnabled === false ? undefined : state.projectIdentity;
16199
+ function memoryProjectPath(state2) {
16200
+ return state2.memoryEnabled === false ? undefined : state2.projectIdentity;
15976
16201
  }
15977
- function resolveWorkspaceRenderContextPi(state, db) {
15978
- const memPath = memoryProjectPath(state);
16202
+ function resolveWorkspaceRenderContextPi(state2, db) {
16203
+ const memPath = memoryProjectPath(state2);
15979
16204
  if (!memPath) {
15980
16205
  return {
15981
16206
  identities: [],
@@ -16051,12 +16276,12 @@ function piImageFromDataUrl(dataUrl) {
16051
16276
  return null;
16052
16277
  return { type: "image", mimeType: match[1], data: match[2] };
16053
16278
  }
16054
- function resolveMuralForM0Pi(state, db, modelKey, budgetTokens) {
16055
- if (state.mural)
16056
- return state.mural;
16057
- if (!state.muralEnabled)
16279
+ function resolveMuralForM0Pi(state2, db, modelKey, budgetTokens) {
16280
+ if (state2.mural)
16281
+ return state2.mural;
16282
+ if (!state2.muralEnabled)
16058
16283
  return;
16059
- return resolveMuralWire(db, memoryProjectPath(state), modelKey, true, budgetTokens);
16284
+ return resolveMuralWire(db, memoryProjectPath(state2), modelKey, true, budgetTokens);
16060
16285
  }
16061
16286
  function cachedInjectionTokenCounts(sessionId, m0, m1) {
16062
16287
  const cached = injectionTokenCountsBySession.get(sessionId);
@@ -16109,17 +16334,17 @@ function trimPiMessagesToCachedBoundary(db, sessionId, piMessages, entryIds) {
16109
16334
  function setCachedBoundary(db, sessionId, boundary) {
16110
16335
  db.prepare("UPDATE session_meta SET cached_m0_last_baseline_end_message_id = ? WHERE session_id = ?").run(boundary, sessionId);
16111
16336
  }
16112
- function getCachedMarkers(db, state, compartmentsForNormalization) {
16113
- const meta = getOrCreateSessionMeta(db, state.sessionId);
16337
+ function getCachedMarkers(db, state2, compartmentsForNormalization) {
16338
+ const meta = getOrCreateSessionMeta(db, state2.sessionId);
16114
16339
  if (!meta.cachedM0Bytes)
16115
16340
  return null;
16116
16341
  if (meta.cachedM0MaxCompartmentSeq === null || meta.cachedM0MaxMemoryId === null || meta.cachedM0MaxMutationId === null || meta.cachedM0MaxMemoryMutationId === null || meta.cachedM0ProjectMemoryEpoch === null || meta.cachedM0ProjectUserProfileVersion === null || meta.cachedM0ProjectDocsHash === null || meta.cachedM0SessionFactsVersion === null || meta.cachedM0MaterializedAt === null || meta.cachedM0UpgradeState === null) {
16117
16342
  return null;
16118
16343
  }
16119
- const compartments = compartmentsForNormalization ?? getCompartments(db, state.sessionId);
16344
+ const compartments = compartmentsForNormalization ?? getRenderableCompartmentsPi(db, state2);
16120
16345
  const cachedUpgradeIdentity = decodeCachedM0UpgradeIdentity(meta.cachedM0UpgradeState);
16121
16346
  const maxCompartmentSeq = normalizeCachedMaxCompartmentSeq(meta.cachedM0MaxCompartmentSeq, compartments);
16122
- const cachedBoundary = getCachedBoundary(db, state.sessionId);
16347
+ const cachedBoundary = getCachedBoundary(db, state2.sessionId);
16123
16348
  const liveBoundary = lastBaselineEndMessageId(compartments);
16124
16349
  if (maxCompartmentSeq >= 0 && cachedBoundary === null && liveBoundary !== null) {
16125
16350
  return null;
@@ -16147,38 +16372,38 @@ function lastBaselineEndMessageId(compartments) {
16147
16372
  const last = compartments.at(-1);
16148
16373
  return last?.endMessageId && last.endMessageId.length > 0 ? last.endMessageId : null;
16149
16374
  }
16150
- function readCurrentMarkers(db, state, projectDocsHash) {
16151
- return readCurrentMarkersFromCompartments(db, state, getCompartments(db, state.sessionId), projectDocsHash);
16375
+ function readCurrentMarkers(db, state2, projectDocsHash) {
16376
+ return readCurrentMarkersFromCompartments(db, state2, getRenderableCompartmentsPi(db, state2), projectDocsHash);
16152
16377
  }
16153
- function readCurrentMarkersFromCompartments(db, state, compartments, projectDocsHash) {
16154
- const memPath = memoryProjectPath(state);
16155
- const workspace = resolveWorkspaceRenderContextPi(state, db);
16378
+ function readCurrentMarkersFromCompartments(db, state2, compartments, projectDocsHash) {
16379
+ const memPath = memoryProjectPath(state2);
16380
+ const workspace = resolveWorkspaceRenderContextPi(state2, db);
16156
16381
  const maxMemoryId = memPath ? workspace.isWorkspaced ? getMaxMemoryIdForProjects(db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryIdForProjects(db, [memPath]) : 0;
16157
16382
  const projectState = memPath ? getProjectState(db, memPath) : undefined;
16158
16383
  const globalState = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH);
16159
16384
  return {
16160
16385
  maxCompartmentSeq: compartments.length > 0 ? compartments.reduce((max, compartment) => compartment.sequence > max ? compartment.sequence : max, EMPTY_MAX_COMPARTMENT_SEQ) : EMPTY_MAX_COMPARTMENT_SEQ,
16161
16386
  maxMemoryId,
16162
- maxMutationId: getMaxM0MutationId(db, state.sessionId) ?? 0,
16387
+ maxMutationId: getMaxM0MutationId(db, state2.sessionId) ?? 0,
16163
16388
  maxMemoryMutationId: memPath ? workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(db, workspace.expandedIdentities) ?? 0 : getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
16164
16389
  projectMemoryEpoch: projectState?.projectMemoryEpoch ?? 0,
16165
16390
  workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(db, workspace.identities) : null,
16166
16391
  projectUserProfileVersion: globalState?.projectUserProfileVersion ?? 0,
16167
- projectDocsHash: projectDocsHash ?? readProjectDocsForPiM0(state).canonicalHash,
16168
- sessionFactsVersion: getSessionFactsVersion(db, state.sessionId),
16392
+ projectDocsHash: projectDocsHash ?? readProjectDocsForPiM0(state2).canonicalHash,
16393
+ sessionFactsVersion: getSessionFactsVersion(db, state2.sessionId),
16169
16394
  materializedAt: Date.now(),
16170
16395
  upgradeState: `${PI_M0_UPGRADE_STATE}:${compartments.some((c) => c.legacy === 1) ? "legacy" : "ready"}`,
16171
16396
  compartmentRenderEpoch: COMPARTMENT_RENDER_EPOCH,
16172
16397
  lastBaselineEndMessageId: lastBaselineEndMessageId(compartments),
16173
- systemHash: (state.hardSignals ?? EMPTY_PI_HARD_SIGNALS).systemHash,
16174
- modelKey: (state.hardSignals ?? EMPTY_PI_HARD_SIGNALS).modelKey,
16175
- projectIdentity: state.projectIdentity
16398
+ systemHash: (state2.hardSignals ?? EMPTY_PI_HARD_SIGNALS).systemHash,
16399
+ modelKey: (state2.hardSignals ?? EMPTY_PI_HARD_SIGNALS).modelKey,
16400
+ projectIdentity: state2.projectIdentity
16176
16401
  };
16177
16402
  }
16178
- function mustMaterializePi(state, db, currentCompartmentsOverride) {
16179
- const meta = getOrCreateSessionMeta(db, state.sessionId);
16180
- const currentCompartments = currentCompartmentsOverride ?? getCompartments(db, state.sessionId);
16181
- const current = readCurrentMarkersFromCompartments(db, state, currentCompartments, meta.cachedM0ProjectDocsHash ?? undefined);
16403
+ function mustMaterializePi(state2, db, currentCompartmentsOverride) {
16404
+ const meta = getOrCreateSessionMeta(db, state2.sessionId);
16405
+ const currentCompartments = currentCompartmentsOverride ?? getRenderableCompartmentsPi(db, state2);
16406
+ const current = readCurrentMarkersFromCompartments(db, state2, currentCompartments, meta.cachedM0ProjectDocsHash ?? undefined);
16182
16407
  if (!meta.cachedM0Bytes)
16183
16408
  return { value: true, reason: "first_render" };
16184
16409
  if (!meta.cachedM1Bytes)
@@ -16186,21 +16411,21 @@ function mustMaterializePi(state, db, currentCompartmentsOverride) {
16186
16411
  if (!decodeCachedM0(meta.cachedM0Bytes)) {
16187
16412
  return { value: true, reason: "cache_invalid" };
16188
16413
  }
16189
- const cached = getCachedMarkers(db, state, currentCompartments);
16414
+ const cached = getCachedMarkers(db, state2, currentCompartments);
16190
16415
  if (cached === null) {
16191
16416
  return { value: true, reason: "cache_invalid" };
16192
16417
  }
16193
16418
  if (cached.compartmentRenderEpoch !== current.compartmentRenderEpoch) {
16194
16419
  return { value: true, reason: "compartment_render_epoch" };
16195
16420
  }
16196
- const hard = state.hardSignals ?? EMPTY_PI_HARD_SIGNALS;
16421
+ const hard = state2.hardSignals ?? EMPTY_PI_HARD_SIGNALS;
16197
16422
  if (hard.modelKey !== "" && hard.modelKey !== (meta.cachedM0ModelKey ?? "")) {
16198
16423
  return { value: true, reason: "model_change" };
16199
16424
  }
16200
16425
  if (hard.systemHash !== "" && hard.systemHash !== (meta.cachedM0SystemHash ?? "")) {
16201
16426
  return { value: true, reason: "system_hash" };
16202
16427
  }
16203
- if (meta.cachedM0ProjectIdentity !== null && meta.cachedM0ProjectIdentity !== state.projectIdentity) {
16428
+ if (meta.cachedM0ProjectIdentity !== null && meta.cachedM0ProjectIdentity !== state2.projectIdentity) {
16204
16429
  return { value: true, reason: "project_change" };
16205
16430
  }
16206
16431
  if (hard.cacheExpired && hard.lastResponseTime > 0 && hard.lastResponseTime > (meta.cachedM0MaterializedAt ?? 0)) {
@@ -16230,9 +16455,9 @@ ${memories.map((memory) => `- ${escapeXmlContent(memory.content)}`).join(`
16230
16455
  `)}
16231
16456
  </${wrapper}>`;
16232
16457
  }
16233
- function renderM0Pi(state, db, projectDocs = readProjectDocsForPiM0(state).renderedBlock, decayPressureMultiplier = 1, memoriesOverride, compartmentsOverride, userProfileOverride, workspaceOverride, mural) {
16234
- const memPath = memoryProjectPath(state);
16235
- const workspace = workspaceOverride ?? resolveWorkspaceRenderContextPi(state, db);
16458
+ function renderM0Pi(state2, db, projectDocs = readProjectDocsForPiM0(state2).renderedBlock, decayPressureMultiplier = 1, memoriesOverride, compartmentsOverride, userProfileOverride, workspaceOverride, mural) {
16459
+ const memPath = memoryProjectPath(state2);
16460
+ const workspace = workspaceOverride ?? resolveWorkspaceRenderContextPi(state2, db);
16236
16461
  const allMemories = memoriesOverride ?? (memPath ? workspace.isWorkspaced ? getMemoriesByProjects(db, workspace.expandedIdentities, ["active", "permanent"], Date.now(), workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(db, memPath, ["active", "permanent"]) : []);
16237
16462
  const memoryRenderOptions = {
16238
16463
  sourceNameByMemoryId: sourceNamesForPiMemories({
@@ -16241,23 +16466,25 @@ function renderM0Pi(state, db, projectDocs = readProjectDocsForPiM0(state).rende
16241
16466
  workspace
16242
16467
  })
16243
16468
  };
16244
- const memories = allMemories.length > 0 ? workspace.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(state.sessionId, allMemories, state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS, workspace, memoryRenderOptions).renderOrder : trimMemoriesToBudgetV2(state.sessionId, allMemories, state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS).renderOrder : allMemories;
16469
+ const memories = allMemories.length > 0 ? workspace.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(state2.sessionId, allMemories, state2.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS, workspace, memoryRenderOptions).renderOrder : trimMemoriesToBudgetV2(state2.sessionId, allMemories, state2.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS).renderOrder : allMemories;
16245
16470
  const memoryBlock = memories.length > 0 ? renderMemoryBlockV2(memories, "project-memory", memoryRenderOptions) : undefined;
16246
- const baseHistoryBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
16471
+ const baseHistoryBudget = state2.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
16247
16472
  const decayed = renderDecayedCompartments({
16248
- compartments: compartmentsOverride ?? getCompartments(db, state.sessionId),
16473
+ compartments: compartmentsOverride ?? getRenderableCompartmentsPi(db, state2),
16249
16474
  historyBudgetTokens: baseHistoryBudget / Math.max(1, decayPressureMultiplier)
16250
16475
  });
16251
16476
  const sections = [];
16252
16477
  if (projectDocs.length > 0)
16253
16478
  sections.push(projectDocs);
16254
- const trimmedProfile = trimUserMemoriesToBudget(userProfileOverride ?? safeGetActiveUserMemoriesPi(db), state.userProfileBudgetTokens ?? DEFAULT_USER_PROFILE_BUDGET_TOKENS);
16479
+ const trimmedProfile = trimUserMemoriesToBudget(userProfileOverride ?? safeGetActiveUserMemoriesPi(db), state2.userProfileBudgetTokens ?? DEFAULT_USER_PROFILE_BUDGET_TOKENS);
16255
16480
  const userProfile = renderUserProfileBlock(db, "user-profile", trimmedProfile);
16256
16481
  if (userProfile.length > 0)
16257
16482
  sections.push(userProfile);
16258
- sections.push(decayed.length > 0 ? `<session-history>
16483
+ if (!state2.compactionOff) {
16484
+ sections.push(decayed.length > 0 ? `<session-history>
16259
16485
  ${decayed}
16260
16486
  </session-history>` : "<session-history></session-history>");
16487
+ }
16261
16488
  if (memoryBlock)
16262
16489
  sections.push(memoryBlock);
16263
16490
  if (mural?.enabled && mural.supportsVision && mural.dataUrl) {
@@ -16269,18 +16496,18 @@ The project memory mural image follows.
16269
16496
 
16270
16497
  `).trim();
16271
16498
  }
16272
- function renderedMemoryIdsForPi(state, memories, workspace, db) {
16499
+ function renderedMemoryIdsForPi(state2, memories, workspace, db) {
16273
16500
  if (memories.length === 0)
16274
16501
  return [];
16275
- const resolvedWorkspace = workspace ?? (db ? resolveWorkspaceRenderContextPi(state, db) : undefined);
16502
+ const resolvedWorkspace = workspace ?? (db ? resolveWorkspaceRenderContextPi(state2, db) : undefined);
16276
16503
  const renderOptions = resolvedWorkspace ? {
16277
16504
  sourceNameByMemoryId: sourceNamesForPiMemories({
16278
16505
  memories,
16279
- projectPath: memoryProjectPath(state),
16506
+ projectPath: memoryProjectPath(state2),
16280
16507
  workspace: resolvedWorkspace
16281
16508
  })
16282
16509
  } : {};
16283
- const trimmed = resolvedWorkspace?.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(state.sessionId, [...memories], state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS, resolvedWorkspace, renderOptions) : trimMemoriesToBudgetV2(state.sessionId, [...memories], state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS);
16510
+ const trimmed = resolvedWorkspace?.isWorkspaced ? trimWorkspaceMemoriesToBudgetV2(state2.sessionId, [...memories], state2.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS, resolvedWorkspace, renderOptions) : trimMemoriesToBudgetV2(state2.sessionId, [...memories], state2.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS);
16284
16511
  return trimmed.renderOrder.map((memory) => memory.id);
16285
16512
  }
16286
16513
  function isTransientSqliteLockError(error) {
@@ -16303,11 +16530,11 @@ class PiMaterializeContentionError extends Error {
16303
16530
  this.name = "PiMaterializeContentionError";
16304
16531
  }
16305
16532
  }
16306
- function readFrozenM0InputsPi(state, db, docs = readProjectDocsForPiM0(state), memoryCutoff) {
16307
- const memPath = memoryProjectPath(state);
16533
+ function readFrozenM0InputsPi(state2, db, docs = readProjectDocsForPiM0(state2), memoryCutoff) {
16534
+ const memPath = memoryProjectPath(state2);
16308
16535
  const read = db.transaction(() => {
16309
- const workspace = resolveWorkspaceRenderContextPi(state, db);
16310
- const compartments = getCompartments(db, state.sessionId);
16536
+ const workspace = resolveWorkspaceRenderContextPi(state2, db);
16537
+ const compartments = getRenderableCompartmentsPi(db, state2);
16311
16538
  const memories = memPath ? workspace.isWorkspaced ? getMemoriesByProjects(db, workspace.expandedIdentities, ["active", "permanent"], memoryCutoff, workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(db, memPath, ["active", "permanent"], memoryCutoff) : [];
16312
16539
  const userProfile = safeGetActiveUserMemoriesPi(db);
16313
16540
  const projectState = memPath ? getProjectState(db, memPath) : undefined;
@@ -16315,72 +16542,72 @@ function readFrozenM0InputsPi(state, db, docs = readProjectDocsForPiM0(state), m
16315
16542
  const markers = {
16316
16543
  maxCompartmentSeq: compartments.reduce((max, compartment) => compartment.sequence > max ? compartment.sequence : max, EMPTY_MAX_COMPARTMENT_SEQ),
16317
16544
  maxMemoryId: memPath ? workspace.isWorkspaced ? getMaxMemoryIdForProjects(db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryIdForProjects(db, [memPath]) : 0,
16318
- maxMutationId: getMaxM0MutationId(db, state.sessionId) ?? 0,
16545
+ maxMutationId: getMaxM0MutationId(db, state2.sessionId) ?? 0,
16319
16546
  maxMemoryMutationId: memPath ? workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(db, workspace.expandedIdentities) ?? 0 : getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
16320
16547
  projectMemoryEpoch: projectState?.projectMemoryEpoch ?? 0,
16321
16548
  workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(db, workspace.identities) : null,
16322
16549
  projectUserProfileVersion: globalState?.projectUserProfileVersion ?? 0,
16323
16550
  projectDocsHash: docs.canonicalHash,
16324
- sessionFactsVersion: getSessionFactsVersion(db, state.sessionId),
16551
+ sessionFactsVersion: getSessionFactsVersion(db, state2.sessionId),
16325
16552
  materializedAt: memoryCutoff ?? Date.now(),
16326
16553
  upgradeState: `${PI_M0_UPGRADE_STATE}:${compartments.some((c) => c.legacy === 1) ? "legacy" : "ready"}`,
16327
16554
  compartmentRenderEpoch: COMPARTMENT_RENDER_EPOCH,
16328
16555
  lastBaselineEndMessageId: lastBaselineEndMessageId(compartments),
16329
- systemHash: (state.hardSignals ?? EMPTY_PI_HARD_SIGNALS).systemHash,
16330
- modelKey: (state.hardSignals ?? EMPTY_PI_HARD_SIGNALS).modelKey,
16331
- projectIdentity: state.projectIdentity
16556
+ systemHash: (state2.hardSignals ?? EMPTY_PI_HARD_SIGNALS).systemHash,
16557
+ modelKey: (state2.hardSignals ?? EMPTY_PI_HARD_SIGNALS).modelKey,
16558
+ projectIdentity: state2.projectIdentity
16332
16559
  };
16333
16560
  return { docs, markers, compartments, memories, userProfile, workspace };
16334
16561
  });
16335
16562
  return read();
16336
16563
  }
16337
- function renderFreshM0PiNonPersisted(state, db) {
16338
- const docs = readProjectDocsForPiM0(state);
16339
- const cachedMaterializedAt = getOrCreateSessionMeta(db, state.sessionId).cachedM0MaterializedAt ?? 0;
16340
- const frozen = readFrozenM0InputsPi(state, db, docs, cachedMaterializedAt);
16564
+ function renderFreshM0PiNonPersisted(state2, db) {
16565
+ const docs = readProjectDocsForPiM0(state2);
16566
+ const cachedMaterializedAt = getOrCreateSessionMeta(db, state2.sessionId).cachedM0MaterializedAt ?? 0;
16567
+ const frozen = readFrozenM0InputsPi(state2, db, docs, cachedMaterializedAt);
16341
16568
  frozen.markers.materializedAt = cachedMaterializedAt;
16342
- const historyBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
16343
- const memoryBudget = state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
16344
- const mural = resolveMuralForM0Pi(state, db, frozen.markers.modelKey, memoryBudget);
16345
- rememberPiMural(state.sessionId, mural);
16569
+ const historyBudget = state2.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
16570
+ const memoryBudget = state2.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
16571
+ const mural = resolveMuralForM0Pi(state2, db, frozen.markers.modelKey, memoryBudget);
16572
+ rememberPiMural(state2.sessionId, mural);
16346
16573
  let dpm = 1;
16347
- let m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile, frozen.workspace, mural);
16574
+ let m0 = renderM0Pi(state2, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile, frozen.workspace, mural);
16348
16575
  let attempts = 0;
16349
16576
  while (historyBudget > 0 && historySliceTokensPi(m0) > historyBudget * 1.05 && attempts < 3) {
16350
16577
  dpm *= 1.15;
16351
- m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile, frozen.workspace, mural);
16578
+ m0 = renderM0Pi(state2, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile, frozen.workspace, mural);
16352
16579
  attempts += 1;
16353
16580
  }
16354
16581
  return {
16355
16582
  m0,
16356
16583
  snapshotMarkers: frozen.markers,
16357
- renderedMemoryIds: renderedMemoryIdsForPi(state, frozen.memories, frozen.workspace, db)
16584
+ renderedMemoryIds: renderedMemoryIdsForPi(state2, frozen.memories, frozen.workspace, db)
16358
16585
  };
16359
16586
  }
16360
- function materializeM0Pi(state, db) {
16361
- const docs = readProjectDocsForPiM0(state);
16587
+ function materializeM0Pi(state2, db) {
16588
+ const docs = readProjectDocsForPiM0(state2);
16362
16589
  const foldMaterializedAt = Date.now();
16363
- const frozen = readFrozenM0InputsPi(state, db, docs, foldMaterializedAt);
16590
+ const frozen = readFrozenM0InputsPi(state2, db, docs, foldMaterializedAt);
16364
16591
  const snapshotMarkers = frozen.markers;
16365
16592
  const snapshotMemories = frozen.memories;
16366
16593
  const snapshotCompartments = frozen.compartments;
16367
16594
  const snapshotUserProfile = frozen.userProfile;
16368
- const renderedMemoryIds = renderedMemoryIdsForPi(state, snapshotMemories, frozen.workspace, db);
16369
- const memoryBudget = state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
16370
- const mural = resolveMuralForM0Pi(state, db, snapshotMarkers.modelKey, memoryBudget);
16595
+ const renderedMemoryIds = renderedMemoryIdsForPi(state2, snapshotMemories, frozen.workspace, db);
16596
+ const memoryBudget = state2.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
16597
+ const mural = resolveMuralForM0Pi(state2, db, snapshotMarkers.modelKey, memoryBudget);
16371
16598
  const frozenMuralDataUrl = mural?.enabled && mural.supportsVision ? mural.dataUrl ?? null : null;
16372
16599
  const frozenMuralHash = mural?.enabled && mural.supportsVision ? mural.contentHash ?? null : null;
16373
16600
  let decayPressureMultiplier = 1;
16374
- let m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile, frozen.workspace, mural);
16375
- const historyBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
16601
+ let m0 = renderM0Pi(state2, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile, frozen.workspace, mural);
16602
+ const historyBudget = state2.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
16376
16603
  let attempts = 0;
16377
16604
  while (historyBudget > 0 && historySliceTokensPi(m0) > historyBudget * 1.05 && attempts < 3) {
16378
16605
  decayPressureMultiplier *= 1.15;
16379
- m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile, frozen.workspace, mural);
16606
+ m0 = renderM0Pi(state2, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile, frozen.workspace, mural);
16380
16607
  attempts += 1;
16381
16608
  }
16382
16609
  const m0Bytes = Buffer.from(m0, "utf8");
16383
- const phase3ProjectDocsHash = readProjectDocsForPiM0(state).canonicalHash;
16610
+ const phase3ProjectDocsHash = readProjectDocsForPiM0(state2).canonicalHash;
16384
16611
  try {
16385
16612
  db.exec("BEGIN IMMEDIATE");
16386
16613
  } catch (error) {
@@ -16390,7 +16617,7 @@ function materializeM0Pi(state, db) {
16390
16617
  throw error;
16391
16618
  }
16392
16619
  try {
16393
- const current = readCurrentMarkers(db, state, phase3ProjectDocsHash);
16620
+ const current = readCurrentMarkers(db, state2, phase3ProjectDocsHash);
16394
16621
  const memoryEpochStale = current.workspaceFingerprint !== null || snapshotMarkers.workspaceFingerprint !== null ? current.workspaceFingerprint !== snapshotMarkers.workspaceFingerprint : current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch;
16395
16622
  const stale = memoryEpochStale || current.projectUserProfileVersion !== snapshotMarkers.projectUserProfileVersion || current.maxCompartmentSeq !== snapshotMarkers.maxCompartmentSeq || current.maxMutationId !== snapshotMarkers.maxMutationId || current.maxMemoryMutationId !== snapshotMarkers.maxMemoryMutationId || current.projectIdentity !== snapshotMarkers.projectIdentity || current.sessionFactsVersion !== snapshotMarkers.sessionFactsVersion || current.upgradeState !== snapshotMarkers.upgradeState;
16396
16623
  if (stale) {
@@ -16398,9 +16625,9 @@ function materializeM0Pi(state, db) {
16398
16625
  throw new PiMaterializeContentionError("snapshot changed before persist");
16399
16626
  }
16400
16627
  snapshotMarkers.materializedAt = foldMaterializedAt;
16401
- const m1Render = renderM1PiWithMetadata(state, db, snapshotMarkers, renderedMemoryIds);
16628
+ const m1Render = renderM1PiWithMetadata(state2, db, snapshotMarkers, renderedMemoryIds);
16402
16629
  const m1Bytes = Buffer.from(m1Render.text, "utf8");
16403
- persistCachedM0(db, state.sessionId, {
16630
+ persistCachedM0(db, state2.sessionId, {
16404
16631
  m0Bytes,
16405
16632
  muralDataUrl: frozenMuralDataUrl,
16406
16633
  muralHash: frozenMuralHash,
@@ -16420,10 +16647,10 @@ function materializeM0Pi(state, db) {
16420
16647
  modelKey: snapshotMarkers.modelKey,
16421
16648
  projectIdentity: snapshotMarkers.projectIdentity
16422
16649
  });
16423
- db.prepare("UPDATE session_meta SET memory_block_count = ?, memory_block_ids = ? WHERE session_id = ?").run(renderedMemoryIds.length, JSON.stringify(renderedMemoryIds), state.sessionId);
16424
- setCachedBoundary(db, state.sessionId, snapshotMarkers.lastBaselineEndMessageId);
16650
+ db.prepare("UPDATE session_meta SET memory_block_count = ?, memory_block_ids = ? WHERE session_id = ?").run(renderedMemoryIds.length, JSON.stringify(renderedMemoryIds), state2.sessionId);
16651
+ setCachedBoundary(db, state2.sessionId, snapshotMarkers.lastBaselineEndMessageId);
16425
16652
  db.exec("COMMIT");
16426
- rememberPiMuralPayload(state.sessionId, frozenMuralDataUrl, frozenMuralHash);
16653
+ rememberPiMuralPayload(state2.sessionId, frozenMuralDataUrl, frozenMuralHash);
16427
16654
  return {
16428
16655
  m0,
16429
16656
  m1: m1Render.text,
@@ -16437,11 +16664,11 @@ function materializeM0Pi(state, db) {
16437
16664
  throw error;
16438
16665
  }
16439
16666
  }
16440
- function materializeM0PiWithRetry(state, db, maxRetries = 3) {
16667
+ function materializeM0PiWithRetry(state2, db, maxRetries = 3) {
16441
16668
  let lastError = null;
16442
16669
  for (let attempt = 0;attempt < maxRetries; attempt++) {
16443
16670
  try {
16444
- return materializeM0Pi(state, db);
16671
+ return materializeM0Pi(state2, db);
16445
16672
  } catch (error) {
16446
16673
  if (!(error instanceof PiMaterializeContentionError))
16447
16674
  throw error;
@@ -16483,10 +16710,10 @@ ${lines.join(`
16483
16710
  count: mutations.length
16484
16711
  };
16485
16712
  }
16486
- function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, compartmentsOverride) {
16713
+ function renderM1PiWithMetadata(state2, db, markers, renderedMemoryIds, compartmentsOverride) {
16487
16714
  const sections = [];
16488
- const workspace = resolveWorkspaceRenderContextPi(state, db);
16489
- const memPath = memoryProjectPath(state);
16715
+ const workspace = resolveWorkspaceRenderContextPi(state2, db);
16716
+ const memPath = memoryProjectPath(state2);
16490
16717
  const memoryUpdates = memPath ? renderMemoryUpdatesBlockPi({
16491
16718
  db,
16492
16719
  projectPath: memPath,
@@ -16496,7 +16723,7 @@ function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, compartme
16496
16723
  }) : { block: undefined, count: 0 };
16497
16724
  if (memoryUpdates.block)
16498
16725
  sections.push(memoryUpdates.block);
16499
- const newCompartments = (compartmentsOverride ?? getCompartments(db, state.sessionId)).filter((compartment) => compartment.sequence > markers.maxCompartmentSeq);
16726
+ const newCompartments = (compartmentsOverride ?? getRenderableCompartmentsPi(db, state2)).filter((compartment) => compartment.sequence > markers.maxCompartmentSeq);
16500
16727
  if (newCompartments.length > 0) {
16501
16728
  const body = newCompartments.map((compartment) => renderCompartmentAtTier(compartment, 1)).join(`
16502
16729
 
@@ -16507,7 +16734,7 @@ ${body}
16507
16734
  }
16508
16735
  const newMemories = memPath ? workspace.isWorkspaced ? readNewMemoriesForM1Union(db, workspace.expandedIdentities, markers.maxMemoryId, markers.materializedAt, workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(db, memPath, ["active", "permanent"], markers.materializedAt).filter((memory) => memory.id > markers.maxMemoryId) : [];
16509
16736
  if (newMemories.length > 0) {
16510
- const memoryBudget = state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
16737
+ const memoryBudget = state2.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
16511
16738
  const memoryRenderOptions = {
16512
16739
  sourceNameByMemoryId: sourceNamesForPiMemories({
16513
16740
  memories: newMemories,
@@ -16515,14 +16742,14 @@ ${body}
16515
16742
  workspace
16516
16743
  })
16517
16744
  };
16518
- const trimmedNewMemories = trimMemoriesToBudgetV2(state.sessionId, newMemories, Math.max(1, Math.floor(memoryBudget * 0.25)), memoryRenderOptions).renderOrder;
16745
+ const trimmedNewMemories = trimMemoriesToBudgetV2(state2.sessionId, newMemories, Math.max(1, Math.floor(memoryBudget * 0.25)), memoryRenderOptions).renderOrder;
16519
16746
  const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories", memoryRenderOptions);
16520
16747
  if (newMemoriesBlock)
16521
16748
  sections.push(newMemoriesBlock);
16522
16749
  }
16523
16750
  const currentUserProfileVersion = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH)?.projectUserProfileVersion ?? 0;
16524
16751
  if (currentUserProfileVersion !== markers.projectUserProfileVersion) {
16525
- const profileBudget = state.userProfileBudgetTokens ?? DEFAULT_USER_PROFILE_BUDGET_TOKENS;
16752
+ const profileBudget = state2.userProfileBudgetTokens ?? DEFAULT_USER_PROFILE_BUDGET_TOKENS;
16526
16753
  const trimmedProfile = trimUserMemoriesToBudget(safeGetActiveUserMemoriesPi(db), Math.max(1, Math.floor(profileBudget * 0.25)));
16527
16754
  const profileBlock = renderUserProfileBlock(db, "new-user-profile", trimmedProfile);
16528
16755
  if (profileBlock)
@@ -16535,7 +16762,10 @@ ${body}
16535
16762
  };
16536
16763
  }
16537
16764
  return {
16538
- text: `<session-history-since>
16765
+ text: state2.compactionOff ? `<knowledge-updates>
16766
+ ${sections.join(`
16767
+ `)}
16768
+ </knowledge-updates>` : `<session-history-since>
16539
16769
  ${sections.join(`
16540
16770
  `)}
16541
16771
  </session-history-since>`,
@@ -16631,8 +16861,8 @@ function cachedPiRowMatchesSnapshot(args) {
16631
16861
  return false;
16632
16862
  return bufferEqualsNullable(args.row.cached_m0_bytes, args.m0Bytes) && rowMarkers.projectMemoryEpoch === args.markers.projectMemoryEpoch && rowMarkers.projectUserProfileVersion === args.markers.projectUserProfileVersion && rowMarkers.maxCompartmentSeq === args.markers.maxCompartmentSeq && rowMarkers.maxMemoryId === args.markers.maxMemoryId && rowMarkers.maxMutationId === args.markers.maxMutationId && rowMarkers.maxMemoryMutationId === args.markers.maxMemoryMutationId && rowMarkers.materializedAt === args.markers.materializedAt && rowMarkers.sessionFactsVersion === args.markers.sessionFactsVersion && (rowMarkers.upgradeState ?? null) === (args.markers.upgradeState ?? null) && rowMarkers.compartmentRenderEpoch === args.markers.compartmentRenderEpoch && (rowMarkers.systemHash ?? "") === (args.markers.systemHash ?? "") && (rowMarkers.modelKey ?? "") === (args.markers.modelKey ?? "") && (rowMarkers.projectIdentity ?? null) === (args.markers.projectIdentity ?? null) && (rowMarkers.workspaceFingerprint ?? null) === (args.markers.workspaceFingerprint ?? null);
16633
16863
  }
16634
- function adoptCachedPiProjectIdentity(db, state) {
16635
- db.prepare("UPDATE session_meta SET cached_m0_project_identity = ? WHERE session_id = ? AND cached_m0_project_identity IS NULL").run(state.projectIdentity, state.sessionId);
16864
+ function adoptCachedPiProjectIdentity(db, state2) {
16865
+ db.prepare("UPDATE session_meta SET cached_m0_project_identity = ? WHERE session_id = ? AND cached_m0_project_identity IS NULL").run(state2.projectIdentity, state2.sessionId);
16636
16866
  }
16637
16867
  function decodeCachedM1(row, sessionId) {
16638
16868
  if (!row.cached_m1_bytes) {
@@ -16653,12 +16883,12 @@ function applyCachedPiRow(args) {
16653
16883
  markers
16654
16884
  };
16655
16885
  }
16656
- function replayCachedM1Pi(db, state, compartmentsForNormalization) {
16657
- const row = readCachedPiM0M1Row(db, state.sessionId);
16886
+ function replayCachedM1Pi(db, state2, compartmentsForNormalization) {
16887
+ const row = readCachedPiM0M1Row(db, state2.sessionId);
16658
16888
  if (!row) {
16659
- throw new PiMaterializeContentionError(`missing cached m[0]/m[1] for ${state.sessionId}`);
16889
+ throw new PiMaterializeContentionError(`missing cached m[0]/m[1] for ${state2.sessionId}`);
16660
16890
  }
16661
- return applyCachedPiRow({ row, state, compartmentsForNormalization });
16891
+ return applyCachedPiRow({ row, state: state2, compartmentsForNormalization });
16662
16892
  }
16663
16893
  function softRefreshCachedM1Pi(args) {
16664
16894
  args.db.exec("BEGIN IMMEDIATE");
@@ -16675,7 +16905,7 @@ function softRefreshCachedM1Pi(args) {
16675
16905
  if (!sibling) {
16676
16906
  throw new PiMaterializeContentionError(`missing sibling cached m[0]/m[1] for ${args.state.sessionId}`);
16677
16907
  }
16678
- const siblingCompartments = getCompartments(args.db, args.state.sessionId);
16908
+ const siblingCompartments = getRenderableCompartmentsPi(args.db, args.state);
16679
16909
  return {
16680
16910
  ...applyCachedPiRow({
16681
16911
  row: sibling,
@@ -16742,9 +16972,9 @@ function prependM0M1Messages(piMessages, m0, m1, mural) {
16742
16972
  timestamp: baseTimestamp - 1
16743
16973
  });
16744
16974
  }
16745
- function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = false) {
16746
- const currentCompartments = getCompartments(db, state.sessionId);
16747
- let decision = mustMaterializePi(state, db, currentCompartments);
16975
+ function injectM0M1Pi(state2, db, piMessages, entryIds, recomputeM1ThisPass = false) {
16976
+ const currentCompartments = getRenderableCompartmentsPi(db, state2);
16977
+ let decision = mustMaterializePi(state2, db, currentCompartments);
16748
16978
  let m0 = "";
16749
16979
  let m1 = PI_M1_PLACEHOLDER;
16750
16980
  let markers = null;
@@ -16755,7 +16985,7 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16755
16985
  let freshFallbackRenderedMemoryIds = null;
16756
16986
  if (decision.value) {
16757
16987
  try {
16758
- const result = materializeM0PiWithRetry(state, db);
16988
+ const result = materializeM0PiWithRetry(state2, db);
16759
16989
  m0 = result.m0;
16760
16990
  m1 = result.m1;
16761
16991
  markers = result.snapshotMarkers;
@@ -16765,30 +16995,30 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16765
16995
  if (!(error instanceof PiMaterializeContentionError))
16766
16996
  throw error;
16767
16997
  try {
16768
- const cached = replayCachedM1Pi(db, state, currentCompartments);
16998
+ const cached = replayCachedM1Pi(db, state2, currentCompartments);
16769
16999
  contentionExhausted = true;
16770
17000
  m0 = cached.m0;
16771
17001
  m1 = cached.m1;
16772
17002
  markers = cached.markers;
16773
- sessionLog(state.sessionId, "pi m[0] materialization contention exhausted; reusing cached m[0]/m[1]");
17003
+ sessionLog(state2.sessionId, "pi m[0] materialization contention exhausted; reusing cached m[0]/m[1]");
16774
17004
  } catch {
16775
- const fresh = renderFreshM0PiNonPersisted(state, db);
17005
+ const fresh = renderFreshM0PiNonPersisted(state2, db);
16776
17006
  m0 = fresh.m0;
16777
17007
  markers = fresh.snapshotMarkers;
16778
17008
  freshFallbackRenderedMemoryIds = fresh.renderedMemoryIds;
16779
17009
  contentionExhausted = true;
16780
- sessionLog(state.sessionId, "pi m[0] materialization contention exhausted with no cached fallback; rendered fresh non-persisted m[0]/m[1]");
17010
+ sessionLog(state2.sessionId, "pi m[0] materialization contention exhausted with no cached fallback; rendered fresh non-persisted m[0]/m[1]");
16781
17011
  }
16782
17012
  }
16783
17013
  } else {
16784
- const meta = getOrCreateSessionMeta(db, state.sessionId);
17014
+ const meta = getOrCreateSessionMeta(db, state2.sessionId);
16785
17015
  m0 = decodeCachedM0(meta.cachedM0Bytes) ?? "";
16786
- rememberPiMuralPayload(state.sessionId, meta.cachedM0MuralDataUrl, meta.cachedM0MuralHash);
16787
- markers = getCachedMarkers(db, state, currentCompartments);
17016
+ rememberPiMuralPayload(state2.sessionId, meta.cachedM0MuralDataUrl, meta.cachedM0MuralHash);
17017
+ markers = getCachedMarkers(db, state2, currentCompartments);
16788
17018
  if (!m0 || !markers) {
16789
17019
  decision = { value: true, reason: "cache_invalid" };
16790
17020
  try {
16791
- const result = materializeM0PiWithRetry(state, db);
17021
+ const result = materializeM0PiWithRetry(state2, db);
16792
17022
  m0 = result.m0;
16793
17023
  m1 = result.m1;
16794
17024
  markers = result.snapshotMarkers;
@@ -16797,30 +17027,30 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16797
17027
  } catch (error) {
16798
17028
  if (!(error instanceof PiMaterializeContentionError))
16799
17029
  throw error;
16800
- const fresh = renderFreshM0PiNonPersisted(state, db);
17030
+ const fresh = renderFreshM0PiNonPersisted(state2, db);
16801
17031
  m0 = fresh.m0;
16802
17032
  markers = fresh.snapshotMarkers;
16803
17033
  freshFallbackRenderedMemoryIds = fresh.renderedMemoryIds;
16804
17034
  contentionExhausted = true;
16805
- sessionLog(state.sessionId, "pi m[0] cache_invalid materialization contention exhausted; rendered fresh non-persisted m[0]/m[1]");
17035
+ sessionLog(state2.sessionId, "pi m[0] cache_invalid materialization contention exhausted; rendered fresh non-persisted m[0]/m[1]");
16806
17036
  }
16807
17037
  }
16808
17038
  }
16809
17039
  if (!markers) {
16810
- throw new PiMaterializeContentionError(`missing m[0] markers for ${state.sessionId}`);
17040
+ throw new PiMaterializeContentionError(`missing m[0] markers for ${state2.sessionId}`);
16811
17041
  }
16812
17042
  if (!materialized && markers.projectIdentity === null) {
16813
- adoptCachedPiProjectIdentity(db, state);
16814
- markers = { ...markers, projectIdentity: state.projectIdentity };
17043
+ adoptCachedPiProjectIdentity(db, state2);
17044
+ markers = { ...markers, projectIdentity: state2.projectIdentity };
16815
17045
  }
16816
17046
  if (materialized) {} else if (contentionExhausted && freshFallbackRenderedMemoryIds) {
16817
- const freshM1 = renderM1PiWithMetadata(state, db, markers, freshFallbackRenderedMemoryIds);
17047
+ const freshM1 = renderM1PiWithMetadata(state2, db, markers, freshFallbackRenderedMemoryIds);
16818
17048
  m1 = freshM1.text;
16819
17049
  memoryUpdateCount = freshM1.memoryUpdateCount;
16820
17050
  m1Recomputed = true;
16821
17051
  } else if (contentionExhausted) {} else if (recomputeM1ThisPass) {
16822
17052
  const refreshed = softRefreshCachedM1Pi({
16823
- state,
17053
+ state: state2,
16824
17054
  db,
16825
17055
  m0Bytes: Buffer.from(m0, "utf8"),
16826
17056
  markers,
@@ -16832,7 +17062,7 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16832
17062
  memoryUpdateCount = refreshed.memoryUpdateCount;
16833
17063
  m1Recomputed = refreshed.recomputed;
16834
17064
  } else {
16835
- const replayed = replayCachedM1Pi(db, state, currentCompartments);
17065
+ const replayed = replayCachedM1Pi(db, state2, currentCompartments);
16836
17066
  m0 = replayed.m0;
16837
17067
  m1 = replayed.m1;
16838
17068
  markers = replayed.markers;
@@ -16840,14 +17070,14 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16840
17070
  const M0_DRIFT_RATIO_FLOOR_TOKENS = 500;
16841
17071
  const M1_DRIFT_RATIO = 0.15;
16842
17072
  const M1_ABSOLUTE_CAP_RATIO = 0.2;
16843
- const m1AbsoluteBudget = (state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS) * M1_ABSOLUTE_CAP_RATIO;
17073
+ const m1AbsoluteBudget = (state2.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS) * M1_ABSOLUTE_CAP_RATIO;
16844
17074
  const m1HasContent = m1 !== PI_M1_PLACEHOLDER;
16845
- const { m0Tokens, m1Tokens } = cachedInjectionTokenCounts(state.sessionId, m0, m1);
17075
+ const { m0Tokens, m1Tokens } = cachedInjectionTokenCounts(state2.sessionId, m0, m1);
16846
17076
  const m1OverAbsoluteCap = m1HasContent && m1Tokens > m1AbsoluteBudget;
16847
17077
  if (!materialized && !contentionExhausted && m1Recomputed && recomputeM1ThisPass && (memoryUpdateCount > 40 || m1OverAbsoluteCap || m1HasContent && m0Tokens >= M0_DRIFT_RATIO_FLOOR_TOKENS && m1Tokens > m0Tokens * M1_DRIFT_RATIO)) {
16848
17078
  decision = { value: true, reason: "drift" };
16849
17079
  try {
16850
- const result = materializeM0PiWithRetry(state, db);
17080
+ const result = materializeM0PiWithRetry(state2, db);
16851
17081
  m0 = result.m0;
16852
17082
  m1 = result.m1;
16853
17083
  markers = result.snapshotMarkers;
@@ -16870,17 +17100,17 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16870
17100
  }
16871
17101
  }
16872
17102
  const skippedVisibleMessages = boundaryId ? trimPiMessagesToBoundary(piMessages, entryIds, boundaryId) : 0;
16873
- const muralWire = m0.includes("<memory-mural>") ? muralForWire(state.sessionId) : undefined;
17103
+ const muralWire = m0.includes("<memory-mural>") ? muralForWire(state2.sessionId) : undefined;
16874
17104
  if (!muralWire)
16875
17105
  m0 = stripMemoryMuralBlock(m0);
16876
17106
  prependM0M1Messages(piMessages, m0, m1, muralWire);
16877
- sessionLog(state.sessionId, `injected m[0]/m[1] into Pi messages (${m0.length} + ${m1.length} bytes, materialized=${materialized}${decision.reason ? ` reason=${decision.reason}` : ""})`);
16878
- const memPath = memoryProjectPath(state);
16879
- const workspace = resolveWorkspaceRenderContextPi(state, db);
17107
+ sessionLog(state2.sessionId, `injected m[0]/m[1] into Pi messages (${m0.length} + ${m1.length} bytes, materialized=${materialized}${decision.reason ? ` reason=${decision.reason}` : ""})`);
17108
+ const memPath = memoryProjectPath(state2);
17109
+ const workspace = resolveWorkspaceRenderContextPi(state2, db);
16880
17110
  const memoryCount = memPath ? workspace.isWorkspaced ? getMemoriesByProjects(db, workspace.expandedIdentities, ["active", "permanent"], Date.now(), workspace.ownIdentities, workspace.shareCategories).length : getMemoriesByProject(db, memPath, ["active", "permanent"]).length : 0;
16881
17111
  return {
16882
17112
  injected: true,
16883
- compartmentCount: getCompartments(db, state.sessionId).length,
17113
+ compartmentCount: currentCompartments.length,
16884
17114
  factCount: 0,
16885
17115
  memoryCount,
16886
17116
  skippedVisibleMessages,
@@ -16942,6 +17172,16 @@ function isSentinelPart(part) {
16942
17172
  return p.type === "text" && typeof p.text === "string" && (p.text === "" || p.text === WHOLE_MESSAGE_PLACEHOLDER_TEXT);
16943
17173
  }
16944
17174
 
17175
+ // src/pi-context-limit.ts
17176
+ function resolvePiUsableContextLimit(args) {
17177
+ const rawContext = isSaneLimit(args.rawContextWindow) ? args.rawContextWindow : undefined;
17178
+ const detected = typeof args.detectedContextLimit === "number" && Number.isFinite(args.detectedContextLimit) && args.detectedContextLimit >= 1024 ? args.detectedContextLimit : undefined;
17179
+ const context = rawContext !== undefined && detected !== undefined ? Math.min(rawContext, detected) : rawContext ?? detected;
17180
+ if (context === undefined)
17181
+ return;
17182
+ return resolveLimit({ context, output: args.model?.maxTokens }, args.model?.provider ?? "unknown", args.model?.id ?? "unknown");
17183
+ }
17184
+
16945
17185
  // src/pi-historian-runner.ts
16946
17186
  import * as crypto2 from "node:crypto";
16947
17187
 
@@ -17745,6 +17985,8 @@ If no candidates, omit the \`<primer_candidates>\` section entirely.
17745
17985
 
17746
17986
  Output valid XML only in this shape:
17747
17987
 
17988
+ Closing tags must match their opening tier tag (e.g. \`<p1>...</p1>\`, never \`<p1>...</p2>\`).
17989
+
17748
17990
  \`\`\`xml
17749
17991
  <output>
17750
17992
  <compartments>
@@ -17961,161 +18203,6 @@ function queueDropsForCompartmentalizedMessages(db, sessionId, upToMessageIndex)
17961
18203
  sessionLog(sessionId, `compartment agent: queued ${dropsQueued} drops for messages 0-${upToMessageIndex}`);
17962
18204
  }
17963
18205
 
17964
- // ../plugin/src/hooks/magic-context/compartment-parser.ts
17965
- var COMPARTMENT_REGEX = /<compartment\s+([^>]*?)\s*>(.*?)<\/compartment>/gs;
17966
- var ATTR_START_REGEX = /\bstart="(\d+)"/;
17967
- var ATTR_END_REGEX = /\bend="(\d+)"/;
17968
- var ATTR_TITLE_REGEX = /\btitle="([^"]*)"/;
17969
- var ATTR_EPISODE_REGEX = /\bepisode_type="([^"]*)"/;
17970
- var ATTR_IMPORTANCE_REGEX = /\bimportance="(\d+)"/;
17971
- function makeTierRegex(n) {
17972
- return new RegExp(`<p${n}\\s*/>|<p${n}\\s*>(.*?)</p${n}>`, "s");
17973
- }
17974
- var TIER_REGEXES = [makeTierRegex(1), makeTierRegex(2), makeTierRegex(3), makeTierRegex(4)];
17975
- var CATEGORY_BLOCK_REGEX = /<(PROJECT_RULES|ARCHITECTURE|CONSTRAINTS|CONFIG_VALUES|NAMING)>(.*?)<\/\1>/gs;
17976
- var FACT_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
17977
- var UNPROCESSED_REGEX = /<unprocessed_from>(\d+)<\/unprocessed_from>/;
17978
- var USER_OBSERVATIONS_REGEX = /<user_observations>(.*?)<\/user_observations>/s;
17979
- var USER_OBS_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
17980
- var PRIMER_CANDIDATES_REGEX = /<primer_candidates>(.*?)<\/primer_candidates>/s;
17981
- var PRIMER_ELEMENT_REGEX = /<primer\s+at_compartment="(\d+)"\s*>(.*?)<\/primer>/gs;
17982
- var PRIMER_ITEM_REGEX = /^\s*(?:\*|-|\d+\.)\s*(.+)$/gm;
17983
- var FACTS_BLOCK_REGEX = /<facts>(.*?)<\/facts>/s;
17984
- var EVENTS_BLOCK_REGEX = /<events>(.*?)<\/events>/s;
17985
- var EVENT_ELEMENT_REGEX = /<([a-z_]+)\s+at_compartment="(\d+)"\s*>(.*?)<\/\1>/gs;
17986
- var EVENT_FIELD_REGEX = /<([a-z_]+)\s*>(.*?)<\/\1>/gs;
17987
- function extractTier(inner, index) {
17988
- const m = inner.match(TIER_REGEXES[index]);
17989
- if (!m)
17990
- return;
17991
- return unescapeXml3((m[1] ?? "").trim());
17992
- }
17993
- function parseCompartmentOutput(text) {
17994
- const compartments = [];
17995
- const facts = [];
17996
- for (const match of text.matchAll(COMPARTMENT_REGEX)) {
17997
- const attrs = match[1];
17998
- const inner = match[2];
17999
- const startMatch = attrs.match(ATTR_START_REGEX);
18000
- const endMatch = attrs.match(ATTR_END_REGEX);
18001
- const titleMatch = attrs.match(ATTR_TITLE_REGEX);
18002
- if (!startMatch || !endMatch || !titleMatch)
18003
- continue;
18004
- const startMessage = parseInt(startMatch[1], 10);
18005
- const endMessage = parseInt(endMatch[1], 10);
18006
- const title = unescapeXml3(titleMatch[1]);
18007
- if (Number.isNaN(startMessage) || Number.isNaN(endMessage) || !title)
18008
- continue;
18009
- const episodeMatch = attrs.match(ATTR_EPISODE_REGEX);
18010
- const importanceMatch = attrs.match(ATTR_IMPORTANCE_REGEX);
18011
- const episodeType = episodeMatch ? unescapeXml3(episodeMatch[1]) : undefined;
18012
- const importance = importanceMatch ? parseInt(importanceMatch[1], 10) : undefined;
18013
- const p1 = extractTier(inner, 0);
18014
- if (typeof p1 === "string" && p1.length > 0) {
18015
- const p2 = extractTier(inner, 1);
18016
- const p3 = extractTier(inner, 2);
18017
- const p4 = extractTier(inner, 3);
18018
- compartments.push({
18019
- startMessage,
18020
- endMessage,
18021
- title,
18022
- content: p1,
18023
- p1,
18024
- p2: typeof p2 === "string" ? p2 : p1,
18025
- p3: typeof p3 === "string" ? p3 : typeof p2 === "string" ? p2 : p1,
18026
- p4: typeof p4 === "string" ? p4 : "",
18027
- importance,
18028
- episodeType
18029
- });
18030
- continue;
18031
- }
18032
- const content = unescapeXml3(inner.trim());
18033
- if (content) {
18034
- compartments.push({
18035
- startMessage,
18036
- endMessage,
18037
- title,
18038
- content,
18039
- importance,
18040
- episodeType
18041
- });
18042
- }
18043
- }
18044
- const factsBlockMatch = text.match(FACTS_BLOCK_REGEX);
18045
- const factsScope = factsBlockMatch ? factsBlockMatch[1] : text.replace(EVENTS_BLOCK_REGEX, "").replace(/<compartment\s+[^>]*?\s*>.*?<\/compartment>/gs, "");
18046
- for (const categoryMatch of factsScope.matchAll(CATEGORY_BLOCK_REGEX)) {
18047
- const category = categoryMatch[1];
18048
- const blockContent = categoryMatch[2];
18049
- for (const itemMatch of blockContent.matchAll(FACT_ITEM_REGEX)) {
18050
- const content = unescapeXml3(itemMatch[1].trim());
18051
- if (content) {
18052
- facts.push({ category, content });
18053
- }
18054
- }
18055
- }
18056
- const unprocessedMatch = text.match(UNPROCESSED_REGEX);
18057
- const unprocessedFrom = unprocessedMatch ? parseInt(unprocessedMatch[1], 10) : null;
18058
- const userObservations = [];
18059
- const userObsMatch = text.match(USER_OBSERVATIONS_REGEX);
18060
- if (userObsMatch) {
18061
- for (const itemMatch of userObsMatch[1].matchAll(USER_OBS_ITEM_REGEX)) {
18062
- const obs = unescapeXml3(itemMatch[1].trim());
18063
- if (obs)
18064
- userObservations.push(obs);
18065
- }
18066
- }
18067
- const primerCandidates = [];
18068
- const primerMatch = text.match(PRIMER_CANDIDATES_REGEX);
18069
- if (primerMatch) {
18070
- const block = primerMatch[1];
18071
- let sawElement = false;
18072
- for (const el of block.matchAll(PRIMER_ELEMENT_REGEX)) {
18073
- sawElement = true;
18074
- const question = unescapeXml3(el[2].trim());
18075
- if (question) {
18076
- primerCandidates.push({
18077
- question,
18078
- originCompartmentIndex: Number.parseInt(el[1], 10)
18079
- });
18080
- }
18081
- }
18082
- if (!sawElement) {
18083
- for (const itemMatch of block.matchAll(PRIMER_ITEM_REGEX)) {
18084
- const question = unescapeXml3(itemMatch[1].trim());
18085
- if (question)
18086
- primerCandidates.push({ question });
18087
- }
18088
- }
18089
- }
18090
- const events = parseEvents(text);
18091
- compartments.sort((a, b) => a.startMessage - b.startMessage);
18092
- return { compartments, facts, events, unprocessedFrom, userObservations, primerCandidates };
18093
- }
18094
- function parseEvents(text) {
18095
- const blockMatch = text.match(EVENTS_BLOCK_REGEX);
18096
- if (!blockMatch)
18097
- return [];
18098
- const block = blockMatch[1];
18099
- const events = [];
18100
- for (const elMatch of block.matchAll(EVENT_ELEMENT_REGEX)) {
18101
- const kind = elMatch[1];
18102
- const atRaw = parseInt(elMatch[2], 10);
18103
- const atCompartment = Number.isNaN(atRaw) ? null : atRaw;
18104
- const fields = {};
18105
- for (const fieldMatch of elMatch[3].matchAll(EVENT_FIELD_REGEX)) {
18106
- const name = fieldMatch[1];
18107
- const value = unescapeXml3(fieldMatch[2].trim());
18108
- if (value)
18109
- fields[name] = value;
18110
- }
18111
- events.push({ kind, atCompartment, fields });
18112
- }
18113
- return events;
18114
- }
18115
- function unescapeXml3(s) {
18116
- return s.replace(/&amp;/g, "&").replace(/&apos;/g, "'").replace(/&quot;/g, '"').replace(/&lt;/g, "<").replace(/&gt;/g, ">");
18117
- }
18118
-
18119
18206
  // ../plugin/src/hooks/magic-context/compartment-runner-mapping.ts
18120
18207
  function tierFieldsOf(c) {
18121
18208
  return {
@@ -20285,6 +20372,7 @@ async function runPiHistorian(deps) {
20285
20372
  twoPass,
20286
20373
  thinkingLevel,
20287
20374
  memoryEnabled,
20375
+ allowHomeProject,
20288
20376
  autoPromote,
20289
20377
  userMemoriesEnabled,
20290
20378
  onPublished,
@@ -20423,7 +20511,7 @@ async function runPiHistorian(deps) {
20423
20511
  rollbackDrainReservation();
20424
20512
  return;
20425
20513
  }
20426
- const projectPath = resolveProjectIdentityForSession(directory);
20514
+ const projectPath = resolveProjectIdentityForSession(directory, allowHomeProject);
20427
20515
  if (!projectPath) {
20428
20516
  rollbackDrainReservation();
20429
20517
  return;
@@ -21427,7 +21515,7 @@ function stripPiProcessedImages(args) {
21427
21515
  }
21428
21516
 
21429
21517
  // src/system-prompt.ts
21430
- import { createHash as createHash9 } from "node:crypto";
21518
+ import { createHash as createHash8 } from "node:crypto";
21431
21519
 
21432
21520
  // ../plugin/src/agents/magic-context-prompt.ts
21433
21521
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
@@ -21580,7 +21668,7 @@ function processSystemPromptForCache(args) {
21580
21668
  sessionLog(sessionId, `system prompt date frozen: real=${liveDate}, using=${stickyDate} (cache-stable pass)`);
21581
21669
  }
21582
21670
  }
21583
- const currentHash = createHash9("md5").update(frozenPrompt).digest("hex");
21671
+ const currentHash = createHash8("md5").update(frozenPrompt).digest("hex");
21584
21672
  const hashChanged = !isFirstHash && currentHash !== previousHash;
21585
21673
  if (hashChanged) {
21586
21674
  sessionLog(sessionId, `system prompt hash changed: ${previousHash} → ${currentHash} (len=${frozenPrompt.length})`);
@@ -22378,7 +22466,6 @@ function extractStableId(msg, index, entryIds) {
22378
22466
  }
22379
22467
 
22380
22468
  // src/context-handler.ts
22381
- var FORCE_MATERIALIZATION_PERCENTAGE = 85;
22382
22469
  var EMERGENCY_BLOCK_PERCENTAGE = 95;
22383
22470
  var FORWARD_PRESSURE_LIMIT_FACTOR = 0.85;
22384
22471
  function applyForwardPressureFloor(trailingPercentage, trailingInputTokens, piUsageTokens, correctedLimit) {
@@ -23000,8 +23087,8 @@ function parsePiFallbackToolOwnerId(ownerMsgId) {
23000
23087
  return { timestamp, role: match[2] ?? "" };
23001
23088
  }
23002
23089
  function databaseIsInTransaction(db) {
23003
- const state = db;
23004
- return state.inTransaction === true || state.isTransaction === true;
23090
+ const state2 = db;
23091
+ return state2.inTransaction === true || state2.isTransaction === true;
23005
23092
  }
23006
23093
  function runImmediateTransaction(db, fn) {
23007
23094
  if (databaseIsInTransaction(db)) {
@@ -23127,7 +23214,7 @@ function registerPiContextHandler(pi, baseOptions) {
23127
23214
  const options = baseOptions.resolveForProject?.(projectDirectory) ?? baseOptions;
23128
23215
  const schedulerConfig = options.scheduler ?? DEFAULT_SCHEDULER_CONFIG;
23129
23216
  const scheduler = schedulerFor(options);
23130
- const projectIdentity = resolveProjectIdentityForSession(projectDirectory) ?? "";
23217
+ const projectIdentity = resolveProjectIdentityForSession(projectDirectory, options.allowHomeProject) ?? "";
23131
23218
  updateSessionProjectTracking(sessionId, projectIdentity, options.db);
23132
23219
  logTransformTiming(sessionId, "findSessionId", tFindSession, `messages=${event.messages.length}`);
23133
23220
  const tEntryBranch = performance.now();
@@ -23149,7 +23236,7 @@ function registerPiContextHandler(pi, baseOptions) {
23149
23236
  const alignedEntryIds = branchLookup?.alignedEntryIds ?? null;
23150
23237
  const resolvedEntryIds = alignedEntryIds?.length === event.messages.length ? alignedEntryIds : branchEntries === null ? null : collectMessageEntryIdsByRef(ctx, event.messages, sessionId, branchEntries);
23151
23238
  const strictEntryIds = resolvedEntryIds ? [...resolvedEntryIds] : null;
23152
- if (strictEntryIds && options.injection) {
23239
+ if (strictEntryIds && options.injection && !options.compactionOff) {
23153
23240
  const removed = trimPiMessagesToCachedBoundary(options.db, sessionId, event.messages, strictEntryIds);
23154
23241
  if (removed > 0) {
23155
23242
  logTransformTiming(sessionId, "cachedBoundaryEarlyTrim", tEntryBranch, `removed=${removed}`);
@@ -23197,6 +23284,30 @@ function registerPiContextHandler(pi, baseOptions) {
23197
23284
  const tMeta = performance.now();
23198
23285
  const sessionMetaForUsage = getOrCreateSessionMeta(options.db, sessionId);
23199
23286
  logTransformTiming(sessionId, "getOrCreateSessionMeta", tMeta);
23287
+ const compactionTransition = reconcilePiCompactionMode({
23288
+ db: options.db,
23289
+ sessionId,
23290
+ compactionOff: options.compactionOff === true,
23291
+ historianRunnable: options.historian !== undefined
23292
+ });
23293
+ if (compactionTransition.recordToWrite) {
23294
+ if (compactionTransition.clearDeferredMarkerState) {
23295
+ clearPiCompactionOffInMemoryState(sessionId);
23296
+ }
23297
+ if (compactionTransition.invalidatedBaseline) {
23298
+ clearPiInjectionTokenCountCache(sessionId);
23299
+ sessionMetaForUsage.cachedM0Bytes = null;
23300
+ sessionMetaForUsage.cachedM1Bytes = null;
23301
+ }
23302
+ let noticeDelivered = compactionTransition.notice === null;
23303
+ if (compactionTransition.notice && ctx.ui?.notify) {
23304
+ ctx.ui.notify(compactionTransition.notice, "info");
23305
+ noticeDelivered = true;
23306
+ }
23307
+ if (noticeDelivered) {
23308
+ commitPiCompactionModeRecord(options.db, sessionId, compactionTransition.recordToWrite);
23309
+ }
23310
+ }
23200
23311
  const usageReset = {
23201
23312
  lastContextPercentage: 0,
23202
23313
  lastInputTokens: 0,
@@ -23240,12 +23351,14 @@ function registerPiContextHandler(pi, baseOptions) {
23240
23351
  usageInputTokens = typeof piUsage?.tokens === "number" ? piUsage.tokens : 0;
23241
23352
  }
23242
23353
  let usageContextLimit = isSaneLimit(piUsage?.contextWindow) ? piUsage.contextWindow : undefined;
23354
+ let detectedContextLimit;
23243
23355
  const tEmergencyRecovery = performance.now();
23244
23356
  let needsEmergencyBump = false;
23245
23357
  let emergencyRecoveryArmed = false;
23246
23358
  try {
23247
- const overflowState = getOverflowState(options.db, sessionId);
23359
+ const overflowState = options.compactionOff ? { detectedContextLimit: 0, needsEmergencyRecovery: false } : getOverflowState(options.db, sessionId);
23248
23360
  if (overflowState.detectedContextLimit > 0) {
23361
+ detectedContextLimit = overflowState.detectedContextLimit;
23249
23362
  usageContextLimit = Math.min(usageContextLimit ?? overflowState.detectedContextLimit, overflowState.detectedContextLimit);
23250
23363
  }
23251
23364
  emergencyRecoveryArmed = overflowState.needsEmergencyRecovery;
@@ -23263,6 +23376,17 @@ function registerPiContextHandler(pi, baseOptions) {
23263
23376
  usageContextLimit = modelWindow;
23264
23377
  }
23265
23378
  }
23379
+ usageContextLimit = resolvePiUsableContextLimit({
23380
+ rawContextWindow: usageContextLimit,
23381
+ model: ctx.model,
23382
+ detectedContextLimit
23383
+ });
23384
+ const effectiveExecuteThresholdPercentage = resolveExecuteThreshold(schedulerConfig.executeThresholdPercentage, modelKey, 65, {
23385
+ tokensConfig: schedulerConfig.executeThresholdTokens,
23386
+ contextLimit: usageContextLimit,
23387
+ sessionId
23388
+ });
23389
+ const { forceMaterializationPercentage } = escalationBands(effectiveExecuteThresholdPercentage);
23266
23390
  if (!usedPersistedUsage && isSaneLimit(usageContextLimit) && usageInputTokens > 0) {
23267
23391
  usagePercentage = usageInputTokens / usageContextLimit * 100;
23268
23392
  }
@@ -23283,7 +23407,7 @@ function registerPiContextHandler(pi, baseOptions) {
23283
23407
  logTransformTiming(sessionId, "schedulerAndUsage", tScheduler);
23284
23408
  const piMessageCount = fullWireMessageCount;
23285
23409
  const looksLikeImportedSession = schedulerDecision === "defer" && usagePercentage === 0 && sessionMeta.lastResponseTime === 0 && piMessageCount >= 50;
23286
- if (looksLikeImportedSession) {
23410
+ if (looksLikeImportedSession && !options.compactionOff) {
23287
23411
  schedulerDecision = "execute";
23288
23412
  sessionLog(sessionId, `transform: large imported session detected (${piMessageCount} messages, no usage baseline) — forcing execute on first pass`);
23289
23413
  }
@@ -23294,7 +23418,7 @@ function registerPiContextHandler(pi, baseOptions) {
23294
23418
  if (storedStableIdScheme < PI_STABLE_ID_SCHEME && !realEntryIdsAvailable) {
23295
23419
  sessionLog(sessionId, `stable-id scheme cutover deferred: real SessionEntry ids unavailable this pass (branch resolution failed) — will retry when getBranch() succeeds`);
23296
23420
  }
23297
- if (stableIdSchemeCutover) {
23421
+ if (stableIdSchemeCutover && !options.compactionOff) {
23298
23422
  schedulerDecision = "execute";
23299
23423
  signalPiPendingMaterialization(sessionId);
23300
23424
  sessionLog(sessionId, `stable-id scheme cutover: stored=${storedStableIdScheme} < current=${PI_STABLE_ID_SCHEME} — forcing execute+materialize this pass`);
@@ -23306,14 +23430,18 @@ function registerPiContextHandler(pi, baseOptions) {
23306
23430
  contextUsage: { percentage: usagePercentage },
23307
23431
  sessionMeta,
23308
23432
  historyRefreshSessions,
23309
- sessionId
23433
+ sessionId,
23434
+ effectiveExecuteThresholdPercentage
23310
23435
  });
23311
- const { midTurnAdjustedSchedulerDecision, sideEffect } = applyMidTurnDeferral({
23436
+ const { midTurnAdjustedSchedulerDecision, sideEffect } = options.compactionOff ? {
23437
+ midTurnAdjustedSchedulerDecision: "defer",
23438
+ sideEffect: "none"
23439
+ } : applyMidTurnDeferral({
23312
23440
  base: schedulerDecisionEarly,
23313
23441
  bypassReason,
23314
23442
  midTurn
23315
23443
  });
23316
- if (sideEffect === "set-flag") {
23444
+ if (sideEffect === "set-flag" && !options.compactionOff) {
23317
23445
  const flagPayload = {
23318
23446
  id: crypto3.randomUUID(),
23319
23447
  reason: `${schedulerDecisionEarly}-${bypassReason}`,
@@ -23323,8 +23451,8 @@ function registerPiContextHandler(pi, baseOptions) {
23323
23451
  }
23324
23452
  schedulerDecision = midTurnAdjustedSchedulerDecision;
23325
23453
  sessionLog(sessionId, `[boundary-exec] base=${schedulerDecisionEarly} bypass=${bypassReason} midTurn=${midTurn} effective=${midTurnAdjustedSchedulerDecision} sideEffect=${sideEffect}`);
23326
- const forceMaterialization = usagePercentage >= FORCE_MATERIALIZATION_PERCENTAGE;
23327
- const isEmergency = usagePercentage >= EMERGENCY_BLOCK_PERCENTAGE;
23454
+ const forceMaterialization = !options.compactionOff && usagePercentage >= forceMaterializationPercentage;
23455
+ const isEmergency = !options.compactionOff && usagePercentage >= EMERGENCY_BLOCK_PERCENTAGE;
23328
23456
  if (isEmergency) {
23329
23457
  const lastNotifiedAt = lastEmergencyNotificationAtMs.get(sessionId) ?? 0;
23330
23458
  const now = Date.now();
@@ -23340,7 +23468,7 @@ function registerPiContextHandler(pi, baseOptions) {
23340
23468
  sessionLog(sessionId, "EMERGENCY: historian wait completed (or timed out)");
23341
23469
  } catch {}
23342
23470
  }
23343
- if (emergencyRecoveryArmed && realUsagePercentageBeforeEmergencyBump < FORCE_MATERIALIZATION_PERCENTAGE && !inFlightHistorian.has(sessionId) && !hasEligiblePiCompartmentHistory(options.db, sessionId)) {
23471
+ if (emergencyRecoveryArmed && realUsagePercentageBeforeEmergencyBump < forceMaterializationPercentage && !inFlightHistorian.has(sessionId) && !hasEligiblePiCompartmentHistory(options.db, sessionId)) {
23344
23472
  try {
23345
23473
  clearEmergencyRecovery(options.db, sessionId);
23346
23474
  sessionLog(sessionId, "EMERGENCY: disarming recovery — no eligible pre-tail history to compact (would otherwise loop at 95%)");
@@ -23392,6 +23520,7 @@ function registerPiContextHandler(pi, baseOptions) {
23392
23520
  stableIdSchemeCutover,
23393
23521
  schedulerDecision,
23394
23522
  forceMaterialization: forceMaterialization || isEmergency,
23523
+ forceMaterializationPercentage,
23395
23524
  contextUsage: {
23396
23525
  percentage: usagePercentage,
23397
23526
  inputTokens: usageInputTokens
@@ -23404,7 +23533,8 @@ function registerPiContextHandler(pi, baseOptions) {
23404
23533
  temporalAwareness: options.injection?.temporalAwareness === true,
23405
23534
  appendCompaction: resolvePiAppendCompaction(ctx),
23406
23535
  readBranchEntries: resolvePiReadBranchEntries(ctx),
23407
- isSubagent: sessionMeta.isSubagent
23536
+ isSubagent: sessionMeta.isSubagent,
23537
+ compactionOff: options.compactionOff === true
23408
23538
  });
23409
23539
  logTransformTiming(sessionId, "runPipeline", tRunPipeline);
23410
23540
  const postPipelineStart = performance.now();
@@ -23428,7 +23558,7 @@ function registerPiContextHandler(pi, baseOptions) {
23428
23558
  }
23429
23559
  logTransformTiming(sessionId, "transformDecisionAndReuseState", tTransformDecision);
23430
23560
  const tHistorianScheduling = performance.now();
23431
- if (options.historian) {
23561
+ if (options.historian && !options.compactionOff) {
23432
23562
  maybeFireHistorian({
23433
23563
  pi,
23434
23564
  ctx,
@@ -23446,22 +23576,24 @@ function registerPiContextHandler(pi, baseOptions) {
23446
23576
  let outputMessages = result.messages;
23447
23577
  const tNoteNudges = performance.now();
23448
23578
  try {
23449
- outputMessages = applyNoteNudges({
23450
- sessionId,
23451
- db: options.db,
23452
- messages: outputMessages,
23453
- projectIdentity,
23454
- entryIds: strictEntryIds,
23455
- entryIdByRef: result.postCommitEntryIdByRef,
23456
- isCacheBusting: isCacheBusting || result.executedWorkThisPass,
23457
- syntheticLeadingCount: result.syntheticLeadingCount
23458
- });
23579
+ if (!options.compactionOff) {
23580
+ outputMessages = applyNoteNudges({
23581
+ sessionId,
23582
+ db: options.db,
23583
+ messages: outputMessages,
23584
+ projectIdentity,
23585
+ entryIds: strictEntryIds,
23586
+ entryIdByRef: result.postCommitEntryIdByRef,
23587
+ isCacheBusting: isCacheBusting || result.executedWorkThisPass,
23588
+ syntheticLeadingCount: result.syntheticLeadingCount
23589
+ });
23590
+ }
23459
23591
  } catch (err) {
23460
23592
  sessionLog(sessionId, `note nudges failed: ${err instanceof Error ? err.message : String(err)}`);
23461
23593
  }
23462
23594
  logTransformTiming(sessionId, "noteNudges", tNoteNudges);
23463
23595
  const tAutoSearch = performance.now();
23464
- if (options.autoSearch?.enabled) {
23596
+ if (options.autoSearch?.enabled && !options.compactionOff) {
23465
23597
  try {
23466
23598
  outputMessages = await runAutoSearchHintForPi({
23467
23599
  sessionId,
@@ -23486,7 +23618,7 @@ function registerPiContextHandler(pi, baseOptions) {
23486
23618
  const tTodoCapture = performance.now();
23487
23619
  try {
23488
23620
  const sessionMetaForTodo = getOrCreateSessionMeta(options.db, sessionId);
23489
- if (!sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
23621
+ if (!options.compactionOff && !sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
23490
23622
  const isCacheBustingForTodo = isCacheBusting || result.executedWorkThisPass;
23491
23623
  outputMessages = injectSyntheticTodowriteForPi({
23492
23624
  db: options.db,
@@ -23504,7 +23636,7 @@ function registerPiContextHandler(pi, baseOptions) {
23504
23636
  const tChannelAccounting = performance.now();
23505
23637
  try {
23506
23638
  const sessionMetaForCh1 = getOrCreateSessionMeta(options.db, sessionId);
23507
- if (!sessionMetaForCh1.isSubagent) {
23639
+ if (!options.compactionOff && !sessionMetaForCh1.isSubagent) {
23508
23640
  const resolvedExecuteThresholdPct = resolveExecuteThreshold(schedulerConfig.executeThresholdPercentage ?? 65, liveModelBySession.get(sessionId), 65, {
23509
23641
  tokensConfig: schedulerConfig.executeThresholdTokens,
23510
23642
  contextLimit: usageContextLimit ?? 0
@@ -23571,7 +23703,7 @@ function registerPiContextHandler(pi, baseOptions) {
23571
23703
  }
23572
23704
  logTransformTiming(sessionId, "workMetrics", tWorkMetrics);
23573
23705
  const tStableIdSchemePersist = performance.now();
23574
- if (stableIdSchemeCutover) {
23706
+ if (stableIdSchemeCutover && !options.compactionOff) {
23575
23707
  persistStableIdSchemeForRun(options.db, sessionId, {
23576
23708
  piStableIdScheme: PI_STABLE_ID_SCHEME
23577
23709
  });
@@ -23596,7 +23728,7 @@ function registerPiContextHandler(pi, baseOptions) {
23596
23728
  sessionLog(sessionId, `transform completed in ${transformElapsedMs.toFixed(1)}ms (${outputMessages.length} messages, ${result.targetCount} targets, watermark: ${result.reasoningWatermark})`);
23597
23729
  return { messages: outputMessages };
23598
23730
  } catch (err) {
23599
- if (isFailClosedBlockingError(err))
23731
+ if (isFailClosedBlockingError(err) && !baseOptions.compactionOff)
23600
23732
  throw err;
23601
23733
  const message = err instanceof Error ? err.message : String(err);
23602
23734
  const stack = err instanceof Error ? err.stack : undefined;
@@ -23753,6 +23885,7 @@ function spawnPiHistorianRun(args) {
23753
23885
  twoPass: historian.twoPass,
23754
23886
  thinkingLevel: historian.thinkingLevel,
23755
23887
  memoryEnabled: historian.memoryEnabled,
23888
+ allowHomeProject: historian.allowHomeProject,
23756
23889
  autoPromote: historian.autoPromote,
23757
23890
  userMemoriesEnabled: historian.userMemoriesEnabled,
23758
23891
  language: historian.language,
@@ -23832,15 +23965,22 @@ function maybeFireHistorian(args) {
23832
23965
  const piUsage = ctx.getContextUsage?.();
23833
23966
  let usageSource;
23834
23967
  usageContextLimit = isSaneLimit(piUsage?.contextWindow) ? piUsage.contextWindow : undefined;
23968
+ let detectedContextLimit;
23835
23969
  if (usageContextLimit === undefined && isSaneLimit(ctx.model?.contextWindow)) {
23836
23970
  usageContextLimit = ctx.model.contextWindow;
23837
23971
  }
23838
23972
  try {
23839
23973
  const overflowState = getOverflowState(db, sessionId);
23840
23974
  if (overflowState.detectedContextLimit > 0) {
23975
+ detectedContextLimit = overflowState.detectedContextLimit;
23841
23976
  usageContextLimit = Math.min(usageContextLimit ?? overflowState.detectedContextLimit, overflowState.detectedContextLimit);
23842
23977
  }
23843
23978
  } catch {}
23979
+ usageContextLimit = resolvePiUsableContextLimit({
23980
+ rawContextWindow: usageContextLimit,
23981
+ model: ctx.model,
23982
+ detectedContextLimit
23983
+ });
23844
23984
  const sessionMetaForUsage = getOrCreateSessionMeta(db, sessionId);
23845
23985
  if (sessionMetaForUsage.lastContextPercentage > 0 && sessionMetaForUsage.lastInputTokens > 0) {
23846
23986
  usage = {
@@ -23879,6 +24019,7 @@ function maybeFireHistorian(args) {
23879
24019
  modelKey,
23880
24020
  usageContextLimit
23881
24021
  });
24022
+ const historianForceMaterializationPercentage = escalationBands(triggerInputs.executeThresholdPercentage).forceMaterializationPercentage;
23882
24023
  const boundaryContextLimit = triggerInputs.contextLimit;
23883
24024
  const resolvePiBoundarySnapshot = (emergencyTailScale) => resolveProtectedTailBoundary(resolveBoundaryContext({
23884
24025
  db,
@@ -23894,7 +24035,7 @@ function maybeFireHistorian(args) {
23894
24035
  }));
23895
24036
  const resolveRunnablePiBoundarySnapshot = () => {
23896
24037
  let snapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot());
23897
- if (!hasRunnableCompartmentWindow(snapshot) && usage.percentage >= 80) {
24038
+ if (!hasRunnableCompartmentWindow(snapshot) && usage.percentage >= historianForceMaterializationPercentage) {
23898
24039
  snapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot(usage.percentage >= 95 ? 0.25 : 0.5));
23899
24040
  }
23900
24041
  return snapshot;
@@ -23938,15 +24079,15 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
23938
24079
  const trigger = checkCompartmentTrigger(db, sessionId, sessionMeta, usage, 0, triggerInputs.executeThresholdPercentage, triggerInputs.triggerBudget, triggerInputs.clearReasoningAge, triggerInputs.commitClusterTrigger, args.activeTags, boundaryContextLimit, () => {
23939
24080
  const messages = provider.readMessages();
23940
24081
  return { messages, absoluteMessageCount: messages.length };
23941
- }, args.taggerFloor);
24082
+ }, args.taggerFloor, { canClearReasoning: true });
23942
24083
  if (!trigger.shouldFire) {
23943
24084
  sessionLog(sessionId, `historian trigger eval: shouldFire=false (no trigger condition met)`);
23944
24085
  try {
23945
24086
  const overflowState = getOverflowState(db, sessionId);
23946
- if (overflowState.needsEmergencyRecovery && usage.percentage < FORCE_MATERIALIZATION_PERCENTAGE && !inFlightHistorian.has(sessionId)) {
24087
+ if (overflowState.needsEmergencyRecovery && usage.percentage < historianForceMaterializationPercentage && !inFlightHistorian.has(sessionId)) {
23947
24088
  boundarySnapshot ??= resolveRunnablePiBoundarySnapshot();
23948
24089
  }
23949
- if (overflowState.needsEmergencyRecovery && usage.percentage < FORCE_MATERIALIZATION_PERCENTAGE && !inFlightHistorian.has(sessionId) && boundarySnapshot !== undefined && !hasRunnableCompartmentWindow(boundarySnapshot)) {
24090
+ if (overflowState.needsEmergencyRecovery && usage.percentage < historianForceMaterializationPercentage && !inFlightHistorian.has(sessionId) && boundarySnapshot !== undefined && !hasRunnableCompartmentWindow(boundarySnapshot)) {
23950
24091
  clearEmergencyRecovery(db, sessionId);
23951
24092
  sessionLog(sessionId, `historian: disarming stale emergency recovery — real pressure ${usage.percentage.toFixed(1)}% with no runnable compartment window (would otherwise bump to 95% every pass)`);
23952
24093
  }
@@ -24034,7 +24175,45 @@ function captureReasoningMutationRollback(messages) {
24034
24175
  }
24035
24176
  };
24036
24177
  }
24178
+ async function runCompactionOffPipeline(args) {
24179
+ let injectionResult = null;
24180
+ if (args.injection) {
24181
+ injectionResult = injectM0M1Pi({
24182
+ sessionId: args.sessionId,
24183
+ projectIdentity: args.projectIdentity,
24184
+ projectDirectory: args.projectDirectory,
24185
+ memoryEnabled: args.injection.memoryEnabled,
24186
+ injectDocs: args.injection.injectDocs,
24187
+ injectionBudgetTokens: args.injection.injectionBudgetTokens,
24188
+ historyBudgetTokens: args.injection.historyBudgetTokens,
24189
+ muralEnabled: args.injection.muralEnabled === true,
24190
+ compactionOff: true
24191
+ }, args.db, args.messages, args.entryIds, false);
24192
+ }
24193
+ return {
24194
+ messages: args.messages,
24195
+ heuristicsExecuted: false,
24196
+ executedWorkThisPass: false,
24197
+ historyInjected: false,
24198
+ syntheticLeadingCount: injectionResult?.syntheticLeadingCount ?? 0,
24199
+ heuristicsResult: null,
24200
+ injectionResult,
24201
+ materialized: injectionResult?.m0Materialized === true,
24202
+ materializeReason: injectionResult?.m0Reason ?? null,
24203
+ droppedTokens: 0,
24204
+ droppedCount: 0,
24205
+ emergency: false,
24206
+ bustedThisPass: injectionResult?.m0Materialized === true,
24207
+ targetCount: 0,
24208
+ reasoningWatermark: args.sessionMeta.clearedReasoningThroughTag ?? 0,
24209
+ activeTags: [],
24210
+ postCommitEntryIdByRef: new Map
24211
+ };
24212
+ }
24037
24213
  async function runPipeline(args) {
24214
+ if (args.compactionOff)
24215
+ return runCompactionOffPipeline(args);
24216
+ const forceMaterializationPercentage = args.forceMaterializationPercentage ?? escalationBands(65).forceMaterializationPercentage;
24038
24217
  let executedWorkThisPass = false;
24039
24218
  let historyWasConsumedThisPass = false;
24040
24219
  let materializationSatisfiedThisPass = false;
@@ -24073,7 +24252,7 @@ async function runPipeline(args) {
24073
24252
  })();
24074
24253
  const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
24075
24254
  const ctxReduceCallable = !args.sessionMeta.isSubagent;
24076
- const canConsumeDeferredLate = args.schedulerDecision === "execute" || args.forceMaterialization === true || args.contextUsage.percentage >= FORCE_MATERIALIZATION_PERCENTAGE;
24255
+ const canConsumeDeferredLate = args.schedulerDecision === "execute" || args.forceMaterialization === true || args.contextUsage.percentage >= forceMaterializationPercentage;
24077
24256
  const deferredMaterializeEligible = canConsumeDeferredLate && deferredMaterializationSessions.has(args.sessionId);
24078
24257
  const piHardSignals = args.injection ? (() => {
24079
24258
  const hardMeta = args.sessionMeta;
@@ -24323,7 +24502,7 @@ async function runPipeline(args) {
24323
24502
  }
24324
24503
  const toolReclaimExecutePass = args.schedulerDecision === "execute";
24325
24504
  const alreadyMutatingThisPass = pendingOpsDidMutate || heuristicOrReasoningDidMutate;
24326
- const emergencyDropEligible = args.forceMaterialization === true || args.contextUsage.percentage >= FORCE_MATERIALIZATION_PERCENTAGE;
24505
+ const emergencyDropEligible = args.forceMaterialization === true || args.contextUsage.percentage >= forceMaterializationPercentage;
24327
24506
  let autoReclaimTargetCount = 0;
24328
24507
  let autoReclaimDidMutate = false;
24329
24508
  if (toolReclaimExecutePass && alreadyMutatingThisPass && !emergencyDropEligible) {
@@ -24710,6 +24889,13 @@ function appendReminderToPiUserMessage(message, reminder) {
24710
24889
  }
24711
24890
  contentArr.push({ type: "text", text: reminder.trimStart() });
24712
24891
  }
24892
+ function clearPiCompactionOffInMemoryState(sessionId) {
24893
+ historyRefreshSessions.delete(sessionId);
24894
+ deferredHistoryRefreshSessions.delete(sessionId);
24895
+ pendingMaterializationSessions.delete(sessionId);
24896
+ deferredMaterializationSessions.delete(sessionId);
24897
+ clearPiChannel1State(sessionId);
24898
+ }
24713
24899
  function clearContextHandlerSession(sessionId) {
24714
24900
  invalidateTrueRawTokenCache({ sessionId, reason: "pi.branch.changed" });
24715
24901
  activeContextHandlerSessions.delete(sessionId);
@@ -24768,6 +24954,14 @@ No active Pi session is available.`,
24768
24954
  });
24769
24955
  return;
24770
24956
  }
24957
+ if (deps.compactionOff) {
24958
+ sendCtxStatusMessage(pi, {
24959
+ title: "/ctx-flush",
24960
+ text: COMPACTION_OFF_COMMAND_UNAVAILABLE,
24961
+ level: "warning"
24962
+ });
24963
+ return;
24964
+ }
24771
24965
  const pendingBefore = getPendingOps(deps.db, sessionId).length;
24772
24966
  const result = executeFlush(deps.db, sessionId);
24773
24967
  signalPiHistoryRefresh(sessionId);
@@ -24798,104 +24992,9 @@ function cleanupHistorianStateFile(path6) {
24798
24992
  unlinkSync(path6);
24799
24993
  } catch {}
24800
24994
  }
24801
- // ../plugin/src/hooks/magic-context/compaction-marker-manager.ts
24802
- var MARKER_SUMMARY_TEXT = "[Compacted by magic-context — session history is managed by the plugin]";
24803
- function dropMarkerSummaryTag(db, sessionId, summaryMessageId) {
24804
- const tagNumber = getTagNumberByMessageId(db, sessionId, `${summaryMessageId}:p0`);
24805
- if (tagNumber !== null)
24806
- updateTagStatus(db, sessionId, tagNumber, "dropped");
24807
- }
24808
- function persistMarkerStateAndDropReplacedTag(db, sessionId, state, replacedSummaryMessageId) {
24809
- db.transaction(() => {
24810
- setPersistedCompactionMarkerState(db, sessionId, state);
24811
- if (replacedSummaryMessageId !== null) {
24812
- dropMarkerSummaryTag(db, sessionId, replacedSummaryMessageId);
24813
- }
24814
- })();
24815
- }
24816
- function getCompartmentEndMessageIdForOrdinal(db, sessionId, endOrdinal) {
24817
- const row = db.prepare(`SELECT end_message_id
24818
- FROM compartments
24819
- WHERE session_id = ? AND end_message = ?
24820
- ORDER BY sequence DESC
24821
- LIMIT 1`).get(sessionId, endOrdinal);
24822
- return typeof row?.end_message_id === "string" && row.end_message_id.length > 0 ? row.end_message_id : null;
24823
- }
24824
- function existingMarkerAlreadyCoversTarget(sessionId, existing, targetOrdinal, targetEndMessageId) {
24825
- if (existing.boundaryOrdinal < targetOrdinal) {
24826
- return false;
24827
- }
24828
- if (existing.boundaryOrdinal === targetOrdinal) {
24829
- const boundaryCompare = compareOpenCodeMessagesByCanonicalOrder(sessionId, existing.boundaryMessageId, targetEndMessageId);
24830
- if (boundaryCompare === null || boundaryCompare > 0) {
24831
- return false;
24832
- }
24833
- if (existing.targetEndMessageId !== null && existing.targetEndMessageId !== targetEndMessageId) {
24834
- return false;
24835
- }
24836
- return true;
24837
- }
24838
- if (existing.targetEndMessageId !== null) {
24839
- const targetCompare = compareOpenCodeMessagesByCanonicalOrder(sessionId, existing.targetEndMessageId, targetEndMessageId);
24840
- if (targetCompare !== null && targetCompare <= 0) {
24841
- return false;
24842
- }
24843
- }
24844
- return true;
24845
- }
24846
- function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, directory) {
24847
- if (getHarness() !== "opencode") {
24848
- return true;
24849
- }
24850
- const targetEndMessageId = getCompartmentEndMessageIdForOrdinal(db, sessionId, lastCompartmentEnd);
24851
- if (!targetEndMessageId) {
24852
- sessionLog(sessionId, `compaction-marker: no compartment endMessageId for ordinal ${lastCompartmentEnd}; preserving existing marker`);
24853
- return false;
24854
- }
24855
- const existing = getPersistedCompactionMarkerState(db, sessionId);
24856
- const removedSummaryMessageId = existing?.summaryMessageId ?? null;
24857
- if (existing) {
24858
- if (existingMarkerAlreadyCoversTarget(sessionId, existing, lastCompartmentEnd, targetEndMessageId)) {
24859
- return true;
24860
- }
24861
- }
24862
- const boundary = findBoundaryUserMessage(sessionId, targetEndMessageId);
24863
- if (!boundary) {
24864
- sessionLog(sessionId, `compaction-marker: no user boundary found at or before endMessageId ${targetEndMessageId} (ordinal ${lastCompartmentEnd}); preserving existing marker`);
24865
- return false;
24866
- }
24867
- if (existing) {
24868
- const removed = removeCompactionMarker(existing);
24869
- if (!removed) {
24870
- sessionLog(sessionId, `compaction-marker: failed to remove old boundary at ordinal ${existing.boundaryOrdinal}; preserving persisted state for retry (not injecting new marker this pass)`);
24871
- return false;
24872
- }
24873
- persistMarkerStateAndDropReplacedTag(db, sessionId, null, removedSummaryMessageId);
24874
- sessionLog(sessionId, `compaction-marker: removed old boundary at ordinal ${existing.boundaryOrdinal}, moving to ${lastCompartmentEnd}`);
24875
- }
24876
- const result = injectCompactionMarker({
24877
- sessionId,
24878
- endOrdinal: lastCompartmentEnd,
24879
- endMessageId: targetEndMessageId,
24880
- summaryText: MARKER_SUMMARY_TEXT,
24881
- directory: directory ?? process.cwd(),
24882
- resolvedBoundary: boundary
24883
- });
24884
- if (result) {
24885
- persistMarkerStateAndDropReplacedTag(db, sessionId, {
24886
- ...result,
24887
- boundaryOrdinal: lastCompartmentEnd,
24888
- targetEndMessageId
24889
- }, removedSummaryMessageId);
24890
- sessionLog(sessionId, `compaction-marker: injected at ordinal ${lastCompartmentEnd}, boundary user msg ${result.boundaryMessageId}`);
24891
- return true;
24892
- }
24893
- return false;
24894
- }
24895
-
24896
24995
  // ../plugin/src/hooks/magic-context/compartment-runner-historian.ts
24897
24996
  import { mkdirSync as mkdirSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
24898
- import { join as join5 } from "node:path";
24997
+ import { join as join4 } from "node:path";
24899
24998
 
24900
24999
  // ../plugin/src/agents/historian.ts
24901
25000
  var HISTORIAN_AGENT = "historian";
@@ -24907,6 +25006,24 @@ function historianResponseDumpDir(directory) {
24907
25006
  return getProjectMagicContextHistorianDir(directory);
24908
25007
  }
24909
25008
  var MAX_HISTORIAN_RETRIES2 = 2;
25009
+ var HISTORIAN_REASONING_PART_TYPES = new Set(["reasoning", "thinking", "redacted_thinking"]);
25010
+ function extractLatestHistorianReasoning(messages) {
25011
+ if (!Array.isArray(messages))
25012
+ return null;
25013
+ const latest = messages.filter((message) => isRecord(message) && isRecord(message.info) && message.info.role === "assistant").sort((left, right) => historianMessageCreatedAt(right) - historianMessageCreatedAt(left))[0];
25014
+ if (!latest || !Array.isArray(latest.parts))
25015
+ return null;
25016
+ return latest.parts.filter(isHistorianReasoningPart).map((part) => part.text).join(`
25017
+ `) || null;
25018
+ }
25019
+ function isHistorianReasoningPart(part) {
25020
+ return isRecord(part) && typeof part.type === "string" && HISTORIAN_REASONING_PART_TYPES.has(part.type) && typeof part.text === "string" && part.text.length > 0;
25021
+ }
25022
+ function historianMessageCreatedAt(message) {
25023
+ if (!isRecord(message.info) || !isRecord(message.info.time))
25024
+ return 0;
25025
+ return typeof message.info.time.created === "number" ? message.info.time.created : 0;
25026
+ }
24910
25027
  async function runValidatedHistorianPass(args) {
24911
25028
  const firstRun = await runHistorianPrompt({
24912
25029
  ...args,
@@ -24972,6 +25089,7 @@ async function runEditorPassOrFallback(args) {
24972
25089
  sessionLog(args.parentSessionId, "historian two-pass: running editor on draft");
24973
25090
  const editorRun = await runHistorianPrompt({
24974
25091
  client: args.client,
25092
+ db: args.db,
24975
25093
  parentSessionId: args.parentSessionId,
24976
25094
  sessionDirectory: args.sessionDirectory,
24977
25095
  prompt: buildHistorianEditorPrompt(args.draftXml),
@@ -24998,6 +25116,7 @@ async function runEditorPassOrFallback(args) {
24998
25116
  async function runHistorianPrompt(args) {
24999
25117
  const {
25000
25118
  client,
25119
+ db,
25001
25120
  parentSessionId,
25002
25121
  sessionDirectory,
25003
25122
  prompt,
@@ -25031,12 +25150,12 @@ async function runHistorianPrompt(args) {
25031
25150
  };
25032
25151
  try {
25033
25152
  sessionLog(parentSessionId, `historian: creating child session (agent=${agentId}, model=${modelOverride ? `${modelOverride.providerID}/${modelOverride.modelID}` : `agent:${agentId}`})`);
25034
- const createResponse = await client.session.create({
25035
- body: {
25036
- parentID: parentSessionId,
25037
- title: "magic-context-compartment"
25038
- },
25039
- query: { directory: sessionDirectory }
25153
+ const createResponse = await createChildSessionWithFence({
25154
+ client,
25155
+ db,
25156
+ parentSessionId,
25157
+ title: "magic-context-compartment",
25158
+ directory: sessionDirectory
25040
25159
  });
25041
25160
  const createdSession = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
25042
25161
  agentSessionId = typeof createdSession?.id === "string" ? createdSession.id : null;
@@ -25084,7 +25203,18 @@ async function runHistorianPrompt(args) {
25084
25203
  preferResponseOnMissingData: true
25085
25204
  });
25086
25205
  const invocationId = recordInvocation5({ status: "completed", messages });
25087
- const result = extractLatestAssistantText(messages);
25206
+ const lengthCapped = hasLengthCappedOutput(messages);
25207
+ const textResult = extractLatestAssistantText(messages);
25208
+ const reasoningResult = textResult ? null : extractLatestHistorianReasoning(messages);
25209
+ if (!textResult && reasoningResult && lengthCapped) {
25210
+ const outputTokens = sumTokensFromChildMessages(messages).output;
25211
+ return {
25212
+ ok: false,
25213
+ error: `historian output length-capped at ${outputTokens} tokens (all reasoning, no text) — set historian.maxTokens or route historian.model to a low-reasoning lane/variant`,
25214
+ invocationId: invocationId ?? undefined
25215
+ };
25216
+ }
25217
+ const result = textResult ?? reasoningResult;
25088
25218
  if (!result) {
25089
25219
  return {
25090
25220
  ok: false,
@@ -25136,6 +25266,7 @@ async function runFallbackHistorianPass(args) {
25136
25266
  args.callbacks?.onModelFallback?.(modelId, i + 1, chain.length);
25137
25267
  const fallbackRun = await runHistorianPrompt({
25138
25268
  client: args.client,
25269
+ db: args.db,
25139
25270
  parentSessionId: args.parentSessionId,
25140
25271
  sessionDirectory: args.sessionDirectory,
25141
25272
  prompt: args.prompt,
@@ -25212,7 +25343,7 @@ function dumpHistorianResponse(sessionId, directory, label, text) {
25212
25343
  ensureCortexKitArtifactGitignore(directory);
25213
25344
  const safeSessionId = sanitizeDumpName(sessionId);
25214
25345
  const safeLabel = sanitizeDumpName(label);
25215
- const dumpPath = join5(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
25346
+ const dumpPath = join4(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
25216
25347
  writeFileSync5(dumpPath, text, "utf8");
25217
25348
  sessionLog(sessionId, "compartment agent: historian response dumped", {
25218
25349
  label,
@@ -25231,116 +25362,6 @@ function sanitizeDumpName(value) {
25231
25362
  return value.replace(/[^a-zA-Z0-9._-]/g, "-");
25232
25363
  }
25233
25364
 
25234
- // ../plugin/src/hooks/magic-context/send-session-notification.ts
25235
- function hasNotificationSessionClient(client) {
25236
- if (client === null || typeof client !== "object")
25237
- return false;
25238
- const candidate = client;
25239
- if (candidate.session === undefined)
25240
- return true;
25241
- if (candidate.session === null || typeof candidate.session !== "object")
25242
- return false;
25243
- const session = candidate.session;
25244
- return (session.prompt === undefined || typeof session.prompt === "function") && (session.promptAsync === undefined || typeof session.promptAsync === "function");
25245
- }
25246
- function inferToastVariant(text) {
25247
- const lower = text.toLowerCase();
25248
- if (lower.includes("error") || lower.includes("failed") || lower.includes("alert"))
25249
- return "error";
25250
- if (lower.includes("warning") || lower.includes("⚠"))
25251
- return "warning";
25252
- if (lower.includes("complete") || lower.includes("success") || lower.includes("✓") || lower.includes("finished"))
25253
- return "success";
25254
- return "info";
25255
- }
25256
- function extractToastTitle(text) {
25257
- const headingMatch = text.match(/^#+\s+(.+)/m);
25258
- if (headingMatch)
25259
- return headingMatch[1].trim();
25260
- const firstLine = text.split(`
25261
- `)[0].trim();
25262
- if (firstLine.length <= 80)
25263
- return firstLine;
25264
- return "Magic Context";
25265
- }
25266
- async function sendIgnoredMessage(client, sessionId, text, params, forcePersist = false) {
25267
- const title = extractToastTitle(text);
25268
- const message = text.length > 200 ? `${text.slice(0, 200)}…` : text;
25269
- const toastVariant = inferToastVariant(text);
25270
- const duration = params.toastDurationMs ?? 5000;
25271
- const { isTuiConnected: checkTui } = await import("./rpc-notifications-td0amw6w.js");
25272
- if (!forcePersist && checkTui(sessionId)) {
25273
- try {
25274
- const { pushNotification } = await import("./rpc-notifications-td0amw6w.js");
25275
- pushNotification("toast", {
25276
- title,
25277
- message,
25278
- variant: toastVariant,
25279
- duration
25280
- }, sessionId);
25281
- return "sent";
25282
- } catch {
25283
- sessionLog(sessionId, "TUI RPC toast enqueue failed, falling back to ignored message");
25284
- }
25285
- }
25286
- const { waitForSafeNotificationTarget } = await import("./safe-notification-target-ng8ygena.js");
25287
- if (await waitForSafeNotificationTarget(client, sessionId) === "skip") {
25288
- sessionLog(sessionId, "notification skipped (session not titled yet)");
25289
- return "skipped";
25290
- }
25291
- if (!hasNotificationSessionClient(client)) {
25292
- sessionLog(sessionId, "session prompt API unavailable for notification");
25293
- return "failed";
25294
- }
25295
- const c = client;
25296
- let agent = params.agent || undefined;
25297
- let variant = params.variant || undefined;
25298
- let model = params.providerId && params.modelId ? { providerID: params.providerId, modelID: params.modelId } : undefined;
25299
- if (!agent || !model || !variant) {
25300
- try {
25301
- const { resolvePromptContext } = await import("./prompt-context-602yq94t.js");
25302
- const resolved = await resolvePromptContext(client, sessionId);
25303
- if (resolved) {
25304
- agent = agent ?? resolved.agent;
25305
- model = model ?? resolved.model;
25306
- variant = variant ?? resolved.variant;
25307
- }
25308
- } catch {}
25309
- }
25310
- const input = {
25311
- path: { id: sessionId },
25312
- body: {
25313
- noReply: true,
25314
- agent,
25315
- model,
25316
- variant,
25317
- parts: [
25318
- {
25319
- type: "text",
25320
- text,
25321
- ignored: true
25322
- }
25323
- ]
25324
- }
25325
- };
25326
- try {
25327
- if (typeof c.session?.prompt === "function") {
25328
- await Promise.resolve(c.session.prompt(input));
25329
- return "sent";
25330
- }
25331
- if (typeof c.session?.promptAsync === "function") {
25332
- await c.session.promptAsync(input);
25333
- return "sent";
25334
- }
25335
- sessionLog(sessionId, "session prompt API unavailable for notification");
25336
- return "failed";
25337
- } catch (error) {
25338
- const msg = getErrorMessage(error);
25339
- sessionLog(sessionId, "failed to send notification:", msg);
25340
- return "failed";
25341
- }
25342
- }
25343
-
25344
25365
  // ../plugin/src/hooks/magic-context/compartment-runner-incremental.ts
25345
25366
  var HISTORIAN_ALERT_COOLDOWN_MS2 = 60 * 1000;
25346
25367
  var lastHistorianAlertBySession2 = new Map;
@@ -25558,6 +25579,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
25558
25579
  emitProgress(`Running historian (pass ${passCount + 1})…`);
25559
25580
  const validatedPass = await runValidatedHistorianPass({
25560
25581
  client,
25582
+ db,
25561
25583
  parentSessionId: sessionId,
25562
25584
  sessionDirectory,
25563
25585
  prompt,
@@ -25977,6 +25999,7 @@ ${chunk.text}`,
25977
25999
  Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${chunk.startIndex}-${chunk.endIndex}.`, notifParams());
25978
26000
  const validatedPass = await runValidatedHistorianPass({
25979
26001
  client,
26002
+ db,
25980
26003
  parentSessionId: sessionId,
25981
26004
  sessionDirectory,
25982
26005
  prompt,
@@ -26332,8 +26355,8 @@ function renderStatusText(ctx, db, sessionId) {
26332
26355
  const inputTokens = typeof usage?.tokens === "number" ? usage.tokens : undefined;
26333
26356
  const pct = typeof usage?.percent === "number" ? usage.percent : undefined;
26334
26357
  const meta = readSessionMetaStatus(db, sessionId);
26335
- const state = renderHistorianState(meta, recompSessions.has(sessionId));
26336
- return `mc: ${inputTokens === undefined ? "--" : fmt(inputTokens)} (${pct === undefined ? "--" : `${Math.round(pct)}%`}) · ${state}`;
26358
+ const state2 = renderHistorianState(meta, recompSessions.has(sessionId));
26359
+ return `mc: ${inputTokens === undefined ? "--" : fmt(inputTokens)} (${pct === undefined ? "--" : `${Math.round(pct)}%`}) · ${state2}`;
26337
26360
  }
26338
26361
  function renderHistorianState(meta, recompActive) {
26339
26362
  const failureCount = meta?.historian_failure_count ?? 0;
@@ -26436,6 +26459,14 @@ No active Pi session is available.`,
26436
26459
  return;
26437
26460
  }
26438
26461
  const currentDeps = deps.resolveRuntimeDeps?.(ctx) ?? deps;
26462
+ if (currentDeps.compactionOff) {
26463
+ sendCtxStatusMessage(pi, {
26464
+ title: "/ctx-recomp",
26465
+ text: COMPACTION_OFF_COMMAND_UNAVAILABLE,
26466
+ level: "warning"
26467
+ });
26468
+ return;
26469
+ }
26439
26470
  const parsed = parseRecompArgs(args);
26440
26471
  if (parsed.kind === "error") {
26441
26472
  sendCtxStatusMessage(pi, {
@@ -26788,7 +26819,7 @@ function contextualizeUpgradeReason(reason) {
26788
26819
  // src/pi-memory-migration.ts
26789
26820
  var MIGRATION_SYSTEM_PROMPT2 = "You re-organize a software project's long-term memory into a stricter taxonomy. " + "Follow the user instructions exactly. Output ONLY the requested XML blocks, nothing else.";
26790
26821
  async function runPiMemoryMigration(deps) {
26791
- const projectPath = resolveProjectIdentityForSession(deps.directory);
26822
+ const projectPath = resolveProjectIdentityForSession(deps.directory, deps.allowHomeProject);
26792
26823
  if (!projectPath) {
26793
26824
  return {
26794
26825
  ran: false,
@@ -26903,6 +26934,14 @@ No active Pi session is available.`,
26903
26934
  return;
26904
26935
  }
26905
26936
  const currentDeps = deps.resolveRuntimeDeps?.(ctx) ?? deps;
26937
+ if (currentDeps.compactionOff) {
26938
+ sendCtxStatusMessage(pi, {
26939
+ title: "/ctx-session-upgrade",
26940
+ text: COMPACTION_OFF_COMMAND_UNAVAILABLE,
26941
+ level: "warning"
26942
+ });
26943
+ return;
26944
+ }
26906
26945
  if (!currentDeps.historianModel) {
26907
26946
  sendCtxStatusMessage(pi, {
26908
26947
  title: "/ctx-session-upgrade",
@@ -26949,6 +26988,7 @@ An upgrade or recomp is already running for this session in the background. Wait
26949
26988
  timeoutMs: currentDeps.historianTimeoutMs,
26950
26989
  thinkingLevel: currentDeps.historianThinkingLevel,
26951
26990
  directory: ctx.cwd,
26991
+ allowHomeProject: currentDeps.allowHomeProject,
26952
26992
  sessionId,
26953
26993
  userMemoriesEnabled: currentDeps.userMemoriesEnabled,
26954
26994
  language: currentDeps.language
@@ -26959,7 +26999,7 @@ An upgrade or recomp is already running for this session in the background. Wait
26959
26999
  }
26960
27000
  };
26961
27001
  if (upgradableCount === 0) {
26962
- const projectPath = resolveProjectIdentityForSession(ctx.cwd);
27002
+ const projectPath = resolveProjectIdentityForSession(ctx.cwd, currentDeps.allowHomeProject);
26963
27003
  if (!projectPath)
26964
27004
  return;
26965
27005
  const migrationPending = migrationEnabled && !isMemoryMigrationDone(currentDeps.db, projectPath);
@@ -27180,8 +27220,8 @@ function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageC
27180
27220
  "### Cache TTL",
27181
27221
  `- Configured: ${meta.cacheTtl}`,
27182
27222
  `- Last response: ${meta.lastResponseTime > 0 ? `${Math.round(elapsed / 1000)}s ago` : "never"}`,
27183
- `- Remaining: ${cacheExpired ? "expired" : `${Math.round(remainingMs / 1000)}s`}`,
27184
- `- Queue will auto-execute: ${cacheExpired ? "yes (cache expired)" : `when TTL expires or context >= ${executeThresholdPercentage}%`}`,
27223
+ `- Remaining: ${cacheExpired ? "expired" : ttlMs === Number.POSITIVE_INFINITY ? "never expires (always-warm lane)" : `${Math.round(remainingMs / 1000)}s`}`,
27224
+ `- Queue will auto-execute: ${cacheExpired ? "yes (cache expired)" : ttlMs === Number.POSITIVE_INFINITY ? `when context >= ${executeThresholdPercentage}%` : `when TTL expires or context >= ${executeThresholdPercentage}%`}`,
27185
27225
  "",
27186
27226
  "### Execute Threshold",
27187
27227
  `- Execute threshold: ${formatExecuteThreshold(thresholdDetail, displayContextLimit)}`,
@@ -27286,7 +27326,7 @@ ${c.content}
27286
27326
  // package.json
27287
27327
  var package_default = {
27288
27328
  name: "@cortexkit/pi-magic-context",
27289
- version: "0.33.0",
27329
+ version: "0.34.0",
27290
27330
  type: "module",
27291
27331
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
27292
27332
  main: "dist/index.js",
@@ -27312,7 +27352,7 @@ var package_default = {
27312
27352
  "README.md"
27313
27353
  ],
27314
27354
  scripts: {
27315
- build: "bun build src/index.ts src/subagent-entry.ts --outdir dist --target node --format esm --splitting --external @earendil-works/pi-coding-agent --external @earendil-works/pi-tui --external @huggingface/transformers --external node:sqlite",
27355
+ build: "rm -f dist/index.js dist/subagent-entry.js dist/*-*.js && bun build src/index.ts src/subagent-entry.ts --outdir dist --target node --format esm --splitting --external @earendil-works/pi-coding-agent --external @earendil-works/pi-tui --external @huggingface/transformers --external node:sqlite",
27316
27356
  typecheck: "tsc --noEmit",
27317
27357
  test: "bun test",
27318
27358
  lint: "biome check src",
@@ -27456,7 +27496,7 @@ function renderInner(s, theme, innerWidth) {
27456
27496
  lines.push(`Upgrade: ${theme.fg("accent", "up to date")}`);
27457
27497
  }
27458
27498
  lines.push(`Pending drops: ${s.pendingOpsCount}`);
27459
- lines.push(`Cache TTL: ${s.cacheTtl} · last response ${s.lastResponseTime > 0 ? `${Math.round((Date.now() - s.lastResponseTime) / 1000)}s ago` : "never"} · ${s.cacheExpired ? theme.fg("warning", "expired") : `${Math.round(s.cacheRemainingMs / 1000)}s remaining`}`);
27499
+ lines.push(`Cache TTL: ${s.cacheTtl} · last response ${s.lastResponseTime > 0 ? `${Math.round((Date.now() - s.lastResponseTime) / 1000)}s ago` : "never"} · ${s.cacheExpired ? theme.fg("warning", "expired") : s.cacheRemainingMs === Number.POSITIVE_INFINITY ? "never expires (always-warm lane)" : `${Math.round(s.cacheRemainingMs / 1000)}s remaining`}`);
27460
27500
  lines.push("");
27461
27501
  lines.push(theme.fg("muted", "Tags"));
27462
27502
  lines.push(`Active ${s.activeTags} (~${formatBytes(s.activeBytes)}) · Dropped ${s.droppedTags} · Total ${s.totalTags}`);
@@ -27498,8 +27538,18 @@ function buildPiStatusDetail(pi, ctx, deps, sessionId) {
27498
27538
  const usage = ctx.getContextUsage?.();
27499
27539
  const meta = getOrCreateSessionMeta(deps.db, sessionId);
27500
27540
  const inputTokens = typeof usage?.tokens === "number" ? usage.tokens : meta.lastInputTokens;
27501
- const usagePercentage = typeof usage?.percent === "number" ? usage.percent : meta.lastContextPercentage;
27502
- const contextLimit = typeof usage?.contextWindow === "number" && usage.contextWindow > 0 ? usage.contextWindow : usagePercentage > 0 ? Math.round(inputTokens / (usagePercentage / 100)) : 0;
27541
+ let detectedContextLimit;
27542
+ try {
27543
+ const detected = getOverflowState(deps.db, sessionId).detectedContextLimit;
27544
+ if (detected > 0)
27545
+ detectedContextLimit = detected;
27546
+ } catch {}
27547
+ const contextLimit = resolvePiUsableContextLimit({
27548
+ rawContextWindow: usage?.contextWindow ?? ctx.model?.contextWindow,
27549
+ model: ctx.model,
27550
+ detectedContextLimit
27551
+ }) ?? (meta.lastContextPercentage > 0 ? Math.round(inputTokens / (meta.lastContextPercentage / 100)) : 0);
27552
+ const usagePercentage = contextLimit > 0 && inputTokens > 0 ? inputTokens / contextLimit * 100 : meta.lastContextPercentage;
27503
27553
  const compartments = getCompartments(deps.db, sessionId);
27504
27554
  const metaRow = readSessionMetaRow(deps.db, sessionId);
27505
27555
  const memoryBlockCount = Number(metaRow?.memory_block_count ?? 0);
@@ -27547,9 +27597,15 @@ ${safeStringify(tool.parameters)}`);
27547
27597
  sessionId
27548
27598
  });
27549
27599
  const cacheTtl = meta.cacheTtl || "5m";
27550
- const cacheTtlMs = parseTtlString(cacheTtl);
27600
+ let cacheTtlMs;
27601
+ try {
27602
+ cacheTtlMs = parseCacheTtl(cacheTtl);
27603
+ } catch {
27604
+ cacheTtlMs = 5 * 60 * 1000;
27605
+ }
27606
+ const neverExpires = cacheTtlMs === Number.POSITIVE_INFINITY;
27551
27607
  const elapsed = meta.lastResponseTime > 0 ? Date.now() - meta.lastResponseTime : 0;
27552
- const cacheRemainingMs = meta.lastResponseTime > 0 ? Math.max(0, cacheTtlMs - elapsed) : cacheTtlMs;
27608
+ const cacheRemainingMs = neverExpires ? Number.POSITIVE_INFINITY : meta.lastResponseTime > 0 ? Math.max(0, cacheTtlMs - elapsed) : cacheTtlMs;
27553
27609
  const cacheExpired = meta.lastResponseTime > 0 && cacheRemainingMs === 0;
27554
27610
  const historyBlockTokens = compartmentTokens + factTokens;
27555
27611
  const historyBudgetPercentage = deps.historyBudgetPercentage ?? 0.15;
@@ -27704,22 +27760,6 @@ function readPendingOpsCount(db, sessionId) {
27704
27760
  return 0;
27705
27761
  }
27706
27762
  }
27707
- function parseTtlString(ttl) {
27708
- const match = ttl.match(/^(\d+)(s|m|h)$/);
27709
- if (!match)
27710
- return 5 * 60 * 1000;
27711
- const val = Number.parseInt(match[1] ?? "5", 10);
27712
- switch (match[2]) {
27713
- case "s":
27714
- return val * 1000;
27715
- case "m":
27716
- return val * 60 * 1000;
27717
- case "h":
27718
- return val * 3600 * 1000;
27719
- default:
27720
- return 5 * 60 * 1000;
27721
- }
27722
- }
27723
27763
  function safeRead(fn, fallback) {
27724
27764
  try {
27725
27765
  return fn();
@@ -27783,7 +27823,18 @@ No active Pi session is available.`,
27783
27823
  }
27784
27824
  const usage = ctx.getContextUsage?.();
27785
27825
  const modelKey = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
27786
- const statusText = executeStatus(currentDeps.db, sessionId, currentDeps.protectedTags ?? 20, currentDeps.executeThresholdPercentage, modelKey, currentDeps.historyBudgetPercentage, currentDeps.commitClusterTrigger, currentDeps.executeThresholdTokens, usage?.contextWindow);
27826
+ let detectedContextLimit;
27827
+ try {
27828
+ const detected = getOverflowState(currentDeps.db, sessionId).detectedContextLimit;
27829
+ if (detected > 0)
27830
+ detectedContextLimit = detected;
27831
+ } catch {}
27832
+ const usableContextLimit = resolvePiUsableContextLimit({
27833
+ rawContextWindow: usage?.contextWindow ?? ctx.model?.contextWindow,
27834
+ model: ctx.model,
27835
+ detectedContextLimit
27836
+ });
27837
+ const statusText = executeStatus(currentDeps.db, sessionId, currentDeps.protectedTags ?? 20, currentDeps.executeThresholdPercentage, modelKey, currentDeps.historyBudgetPercentage, currentDeps.commitClusterTrigger, currentDeps.executeThresholdTokens, usableContextLimit);
27787
27838
  const details = buildStatusDetails(currentDeps, sessionId);
27788
27839
  sendCtxStatusMessage(pi, { title: "/ctx-status", text: statusText, level: "info" }, details);
27789
27840
  } catch (error) {
@@ -27885,6 +27936,14 @@ No active Pi session is available.`,
27885
27936
  return;
27886
27937
  }
27887
27938
  const currentDeps = deps.resolveRuntimeDeps?.(ctx) ?? deps;
27939
+ if (currentDeps.compactionOff) {
27940
+ sendCtxStatusMessage(pi, {
27941
+ title: "/ctx-wrapup",
27942
+ text: COMPACTION_OFF_COMMAND_UNAVAILABLE,
27943
+ level: "warning"
27944
+ });
27945
+ return;
27946
+ }
27888
27947
  const sessionMeta = getOrCreateSessionMeta(currentDeps.db, sessionId);
27889
27948
  if (sessionMeta.isSubagent) {
27890
27949
  sendCtxStatusMessage(pi, {
@@ -27937,7 +27996,7 @@ async function runPiWrapup(pi, deps, ctx, sessionId, messagesToKeep) {
27937
27996
  const unregister = setRawMessageProvider(sessionId, provider);
27938
27997
  let holderId = "";
27939
27998
  try {
27940
- const contextLimit = resolvePiContextLimit(ctx);
27999
+ const contextLimit = resolvePiContextLimit(ctx, deps.db, sessionId);
27941
28000
  const modelKey = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
27942
28001
  const executeThresholdPercentage = resolveExecuteThreshold(deps.executeThresholdPercentage ?? 65, modelKey, 65, {
27943
28002
  tokensConfig: deps.executeThresholdTokens,
@@ -28166,15 +28225,19 @@ Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments.
28166
28225
  unregister();
28167
28226
  }
28168
28227
  }
28169
- function resolvePiContextLimit(ctx) {
28228
+ function resolvePiContextLimit(ctx, db, sessionId) {
28170
28229
  const usage = ctx.getContextUsage?.();
28171
- if (typeof usage?.contextWindow === "number" && usage.contextWindow > 0) {
28172
- return usage.contextWindow;
28173
- }
28174
- if (typeof ctx.model?.contextWindow === "number" && ctx.model.contextWindow > 0) {
28175
- return ctx.model.contextWindow;
28176
- }
28177
- return 128000;
28230
+ let detectedContextLimit;
28231
+ try {
28232
+ const detected = getOverflowState(db, sessionId).detectedContextLimit;
28233
+ if (detected > 0)
28234
+ detectedContextLimit = detected;
28235
+ } catch {}
28236
+ return resolvePiUsableContextLimit({
28237
+ rawContextWindow: usage?.contextWindow ?? ctx.model?.contextWindow,
28238
+ model: ctx.model,
28239
+ detectedContextLimit
28240
+ }) ?? 128000;
28178
28241
  }
28179
28242
  async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker, maxWaitMs) {
28180
28243
  const waitStartedAt = Date.now();
@@ -28194,15 +28257,15 @@ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarke
28194
28257
  function estimateChunks(tokens, chunkTokens) {
28195
28258
  return Math.max(1, Math.ceil(Math.max(0, tokens) / Math.max(1, chunkTokens)));
28196
28259
  }
28197
- function formatExistingWrapup(state) {
28198
- if (!state) {
28260
+ function formatExistingWrapup(state2) {
28261
+ if (!state2) {
28199
28262
  return `## Magic Wrapup — Skipped
28200
28263
 
28201
28264
  Another /ctx-wrapup is already compacting this session. Wait for it to finish, then try again.`;
28202
28265
  }
28203
28266
  return `## Magic Wrapup — Skipped
28204
28267
 
28205
- Another /ctx-wrapup is already compacting this session (chunk ${state.chunkIndex}/${Math.max(state.chunkIndex, state.expectedChunks)}, wrapped through message ${state.lastCompartmentEnd}). Wait for it to finish, then try again.`;
28268
+ Another /ctx-wrapup is already compacting this session (chunk ${state2.chunkIndex}/${Math.max(state2.chunkIndex, state2.expectedChunks)}, wrapped through message ${state2.lastCompartmentEnd}). Wait for it to finish, then try again.`;
28206
28269
  }
28207
28270
  function readBranchEntries(ctx) {
28208
28271
  const getBranch = ctx.sessionManager.getBranch;
@@ -28326,15 +28389,43 @@ function stripTagPrefixFromAssistantMessage(message) {
28326
28389
 
28327
28390
  // src/index.ts
28328
28391
  var PREFIX2 = "[magic-context][pi]";
28329
- function resolveCurrentProject(ctx) {
28392
+ var PI_ACTIVE_LATCH = Symbol.for("magic-context.pi.active");
28393
+ function isPiMagicContextActiveInProcess() {
28394
+ return globalThis[PI_ACTIVE_LATCH] === true;
28395
+ }
28396
+ function markPiMagicContextActive() {
28397
+ globalThis[PI_ACTIVE_LATCH] = true;
28398
+ }
28399
+ function clearPiMagicContextActive() {
28400
+ try {
28401
+ delete globalThis[PI_ACTIVE_LATCH];
28402
+ } catch {
28403
+ globalThis[PI_ACTIVE_LATCH] = undefined;
28404
+ }
28405
+ }
28406
+ function resolveCurrentProject(ctx, allowHomeProject = false) {
28330
28407
  const projectDir = ctx.cwd;
28331
- const projectIdentity = resolveProjectIdentityForSession(projectDir) ?? "";
28408
+ const projectIdentity = resolveProjectIdentityForSession(projectDir, allowHomeProject) ?? "";
28332
28409
  return { projectDir, projectIdentity };
28333
28410
  }
28334
28411
  function signalPiDeferredCompactionMarkerDrain(sessionId) {
28335
28412
  signalPiDeferredHistoryRefresh(sessionId);
28336
28413
  signalPiDeferredMaterialization(sessionId);
28337
28414
  }
28415
+ async function handlePiSessionBeforeCompact(args) {
28416
+ try {
28417
+ const sessionId = args.ctx.sessionManager?.getSessionId?.();
28418
+ if (typeof sessionId === "string" && sessionId.length > 0) {
28419
+ clearPiM0Cache(args.db, sessionId, "session_before_compact");
28420
+ }
28421
+ } catch {}
28422
+ if (args.compactionOff) {
28423
+ info("session_before_compact: native Pi compaction proceeds (compaction-off mode)");
28424
+ return;
28425
+ }
28426
+ info("session_before_compact: cancelling — magic-context owns compaction");
28427
+ return { cancel: true };
28428
+ }
28338
28429
  function persistPiMessageEndModelMeta(args) {
28339
28430
  if (!args.message || typeof args.message !== "object")
28340
28431
  return;
@@ -28440,7 +28531,10 @@ var __test = {
28440
28531
  logPiConfigLoad,
28441
28532
  resetLoggedPiConfigDirs() {
28442
28533
  loggedPiConfigDirs.clear();
28443
- }
28534
+ },
28535
+ isPiMagicContextActiveInProcess,
28536
+ markPiMagicContextActive,
28537
+ clearPiMagicContextActive
28444
28538
  };
28445
28539
  function formatTokens(value) {
28446
28540
  return value.toLocaleString();
@@ -28456,23 +28550,28 @@ function getPiMessageModel(message) {
28456
28550
  };
28457
28551
  }
28458
28552
  function resolvePiPressureContextLimit(args) {
28459
- let effectiveContextLimit = isSaneLimit(args.piContextWindow) ? args.piContextWindow : 0;
28553
+ let detectedContextLimit;
28460
28554
  try {
28461
28555
  const overflowState = getOverflowState(args.db, args.sessionId);
28462
28556
  if (overflowState.detectedContextLimit > 0) {
28463
- effectiveContextLimit = effectiveContextLimit > 0 ? Math.min(effectiveContextLimit, overflowState.detectedContextLimit) : overflowState.detectedContextLimit;
28557
+ detectedContextLimit = overflowState.detectedContextLimit;
28464
28558
  }
28465
28559
  } catch (err) {
28466
28560
  warn("message_end: getOverflowState failed:", err);
28467
28561
  }
28468
- return effectiveContextLimit;
28562
+ return resolvePiUsableContextLimit({
28563
+ rawContextWindow: args.piContextWindow,
28564
+ model: args.model,
28565
+ detectedContextLimit
28566
+ }) ?? 0;
28469
28567
  }
28470
28568
  async function persistPiPressureFromMessageEnd(args) {
28471
28569
  const { provider, model } = getPiMessageModel(args.message);
28472
28570
  const effectiveContextLimit = resolvePiPressureContextLimit({
28473
28571
  db: args.db,
28474
28572
  sessionId: args.sessionId,
28475
- piContextWindow: args.piContextWindow
28573
+ piContextWindow: args.piContextWindow,
28574
+ model: args.piModel ?? { provider, id: model }
28476
28575
  });
28477
28576
  const usage = extractAssistantUsage(args.message);
28478
28577
  const pressure = computePiPressure(usage, effectiveContextLimit);
@@ -28499,8 +28598,8 @@ async function persistPiPressureFromMessageEnd(args) {
28499
28598
  }
28500
28599
  } else if (typeof args.piTokens === "number") {
28501
28600
  updates.lastInputTokens = args.piTokens;
28502
- if (args.piContextWindow > 0) {
28503
- updates.lastContextPercentage = args.piTokens / args.piContextWindow * 100;
28601
+ if (effectiveContextLimit > 0) {
28602
+ updates.lastContextPercentage = args.piTokens / effectiveContextLimit * 100;
28504
28603
  }
28505
28604
  }
28506
28605
  updateSessionMeta(args.db, args.sessionId, updates);
@@ -28527,7 +28626,8 @@ function resolveSidekickFromConfig(config) {
28527
28626
  timeoutMs: sidekick.timeout_ms,
28528
28627
  thinking_level: sidekick.thinking_level,
28529
28628
  fallbackModels: resolveFallbackChain(sidekick.fallback_models),
28530
- language: config.language
28629
+ language: config.language,
28630
+ allowHomeProject: config.allow_home_project
28531
28631
  };
28532
28632
  }
28533
28633
  function resolveHistorianFromConfig(config) {
@@ -28557,7 +28657,8 @@ function resolveHistorianFromConfig(config) {
28557
28657
  memoryEnabled: config.memory.enabled,
28558
28658
  autoPromote: config.memory.auto_promote,
28559
28659
  userMemoriesEnabled: userMemoryCollectionEnabled(config.dreamer),
28560
- language: config.language
28660
+ language: config.language,
28661
+ allowHomeProject: config.allow_home_project
28561
28662
  };
28562
28663
  }
28563
28664
  function resolveAutoSearchFromConfig(config) {
@@ -28577,9 +28678,22 @@ async function src_default(pi) {
28577
28678
  log(`${PREFIX2} subagent child detected (${MAGIC_CONTEXT_PI_SUBAGENT_ENV}=1); skipping full extension registration`);
28578
28679
  return;
28579
28680
  }
28681
+ if (isPiMagicContextActiveInProcess()) {
28682
+ log(`${PREFIX2} in-process re-init detected (Magic Context already active in this process); skipping full extension registration`);
28683
+ return;
28684
+ }
28685
+ markPiMagicContextActive();
28580
28686
  beginBootQuietPeriod();
28687
+ const bootProjectDir = process.cwd();
28688
+ ensureConfigLocationsMigrated(bootProjectDir);
28689
+ const bootConfig = loadPiConfig({ cwd: bootProjectDir });
28690
+ setStoragePrivatePermissionEnforcement(bootConfig.config.storage.enforce_private_permissions);
28691
+ setSqlitePragmaConfig({
28692
+ cacheSizeMb: bootConfig.config.sqlite.cache_size_mb,
28693
+ mmapSizeMb: bootConfig.config.sqlite.mmap_size_mb
28694
+ });
28581
28695
  const storageDir = getMagicContextStorageDir();
28582
- const dbPath = join6(storageDir, "context.db");
28696
+ const dbPath = join5(storageDir, "context.db");
28583
28697
  let db;
28584
28698
  let openFailureCause = null;
28585
28699
  try {
@@ -28596,16 +28710,26 @@ async function src_default(pi) {
28596
28710
  info("plugin DISABLED via config (enabled: false) — skipping registration");
28597
28711
  return;
28598
28712
  }
28713
+ const migration = getMigrationOnOpenRefusal();
28714
+ const blockingProcesses = migration?.serverPids.map((pid) => ({
28715
+ harness: "OpenCode server",
28716
+ pid
28717
+ })) ?? [];
28599
28718
  const fence = getSchemaFenceRejection();
28600
- const reason = fence ? {
28719
+ const reason = migration && blockingProcesses.length > 0 ? {
28720
+ kind: "migration_guard",
28721
+ persistedVersion: migration.persistedVersion,
28722
+ supportedVersion: migration.supportedVersion,
28723
+ blockingProcesses
28724
+ } : fence ? {
28601
28725
  kind: "schema_fence",
28602
28726
  persistedVersion: fence.persistedVersion,
28603
28727
  supportedVersion: fence.supportedVersion
28604
28728
  } : {
28605
28729
  kind: "storage_failure",
28606
- cause: openFailureCause ?? `storage unavailable at ${dbPath} (cache schema newer than this binary, or open failed)`
28730
+ cause: migration?.unreadableFile ? `migration guard could not read RPC discovery file ${migration.unreadableFile}` : openFailureCause ?? `storage unavailable at ${dbPath} (cache schema newer than this binary, or open failed)`
28607
28731
  };
28608
- if (early.config.fail_closed_blocking === false) {
28732
+ if (early.config.fail_closed_blocking === false || !isCompactionEnabled(early.config)) {
28609
28733
  warn(`Magic Context (pi) storage unavailable at ${dbPath}: ${formatFailClosedBlockingMessage(reason)}. ` + "fail_closed_blocking=false — degrading silently (hooks not registered).");
28610
28734
  return;
28611
28735
  }
@@ -28653,26 +28777,30 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28653
28777
  })();
28654
28778
  }, 0);
28655
28779
  const projectDir = process.cwd();
28656
- const projectIdentity = resolveProjectIdentityForSession(projectDir) ?? "";
28657
28780
  const seenDreamerProjectIdentities = new Set;
28658
- if (projectIdentity)
28659
- seenDreamerProjectIdentities.add(projectIdentity);
28660
- try {
28661
- const pendingPiMarkerSessions = getSessionsWithPendingPiMarker(db);
28662
- for (const sid of pendingPiMarkerSessions) {
28663
- signalPiDeferredCompactionMarkerDrain(sid);
28664
- }
28665
- if (pendingPiMarkerSessions.length > 0) {
28666
- log(`${PREFIX2} rehydrated ${pendingPiMarkerSessions.length} Pi deferred compaction marker session(s)`);
28667
- }
28668
- } catch (err) {
28669
- warn(`Magic Context (pi) failed to rehydrate deferred Pi compaction markers: ${err instanceof Error ? err.message : String(err)}`);
28670
- }
28671
- info(`loaded v${PLUGIN_VERSION} | harness=pi | db=${dbPath} | ` + `project=${projectIdentity} | dir=${projectDir}`);
28672
28781
  ensureConfigLocationsMigrated(projectDir);
28673
28782
  const { config, warnings, loadedFromPaths } = loadPiConfig({
28674
28783
  cwd: projectDir
28675
28784
  });
28785
+ const projectIdentity = resolveProjectIdentityForSession(projectDir, config.allow_home_project) ?? "";
28786
+ if (projectIdentity)
28787
+ seenDreamerProjectIdentities.add(projectIdentity);
28788
+ info(`loaded v${PLUGIN_VERSION} | harness=pi | db=${dbPath} | ` + `project=${projectIdentity} | dir=${projectDir}`);
28789
+ const compactionOff = !isCompactionEnabled(config);
28790
+ setCtxReduceRegisteredGlobally(!compactionOff);
28791
+ if (!compactionOff) {
28792
+ try {
28793
+ const pendingPiMarkerSessions = getSessionsWithPendingPiMarker(db);
28794
+ for (const sid of pendingPiMarkerSessions) {
28795
+ signalPiDeferredCompactionMarkerDrain(sid);
28796
+ }
28797
+ if (pendingPiMarkerSessions.length > 0) {
28798
+ log(`${PREFIX2} rehydrated ${pendingPiMarkerSessions.length} Pi deferred compaction marker session(s)`);
28799
+ }
28800
+ } catch (err) {
28801
+ warn(`Magic Context (pi) failed to rehydrate deferred Pi compaction markers: ${err instanceof Error ? err.message : String(err)}`);
28802
+ }
28803
+ }
28676
28804
  configurePiSubagentExtensions(config.pi?.subagent_extensions);
28677
28805
  logPiConfigLoad({
28678
28806
  dir: projectDir,
@@ -28680,6 +28808,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28680
28808
  warnings,
28681
28809
  dedupe: true
28682
28810
  });
28811
+ setStoragePrivatePermissionEnforcement(config.storage.enforce_private_permissions);
28683
28812
  setSqlitePragmaConfig({
28684
28813
  cacheSizeMb: config.sqlite.cache_size_mb,
28685
28814
  mmapSizeMb: config.sqlite.mmap_size_mb
@@ -28719,6 +28848,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28719
28848
  language: cfg.language,
28720
28849
  autoSearch: auto,
28721
28850
  resolveForProject: resolveContextOptionsForProject,
28851
+ compactionOff,
28852
+ allowHomeProject: cfg.allow_home_project,
28722
28853
  maybeAutoEmbedSession: (sessionId, dir, identity) => {
28723
28854
  maybeAutoEmbedPiSession({
28724
28855
  db: database,
@@ -28740,7 +28871,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28740
28871
  hist.onStatusChange = (ctx) => {
28741
28872
  updateStatusLine(ctx, {
28742
28873
  db: database,
28743
- projectIdentity: resolveCurrentProject(ctx).projectIdentity ?? ""
28874
+ projectIdentity: resolveCurrentProject(ctx, cfg.allow_home_project).projectIdentity ?? ""
28744
28875
  });
28745
28876
  };
28746
28877
  }
@@ -28770,14 +28901,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28770
28901
  dedupe: true
28771
28902
  });
28772
28903
  const switchedConfig = switchedLoad.config;
28773
- const switchedIdentity = identityOverride ?? resolveProjectIdentityForSession(dir) ?? "";
28904
+ const switchedIdentity = identityOverride ?? resolveProjectIdentityForSession(dir, switchedConfig.allow_home_project) ?? "";
28774
28905
  const built = buildProjectDeps(dir, switchedIdentity, switchedConfig);
28775
28906
  projectDepsByDir.set(dir, built);
28776
28907
  return built;
28777
28908
  }
28778
28909
  function resolveCurrentProjectDeps(ctx) {
28779
- const currentProject = resolveCurrentProject(ctx);
28780
- return resolveProjectDepsForDir(currentProject.projectDir, currentProject.projectIdentity);
28910
+ return resolveProjectDepsForDir(ctx.cwd);
28781
28911
  }
28782
28912
  function resolveContextOptionsForProject(dir) {
28783
28913
  return resolveProjectDepsForDir(dir).contextOptions;
@@ -28793,11 +28923,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28793
28923
  memoryToolEnabled: true,
28794
28924
  protectedTags: config.protected_tags ?? 20,
28795
28925
  resolveProtectedTags: (ctx) => resolveCurrentProjectDeps(ctx).config.protected_tags ?? 20,
28926
+ resolveProjectIdentity: (ctx) => resolveCurrentProjectDeps(ctx).projectIdentity,
28796
28927
  dreamerEnabled: isDreamerRunnable(config),
28797
28928
  resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
28798
- todowriteEnabled
28929
+ todowriteEnabled,
28930
+ compactionOff
28799
28931
  });
28800
- info(todowriteEnabled ? "registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, todowrite, ctx_reduce; registered /todos" : "registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, ctx_reduce (todowrite disabled)");
28932
+ info(compactionOff ? todowriteEnabled ? "registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, todowrite; registered /todos (ctx_reduce unavailable in compaction-off mode)" : "registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand (ctx_reduce unavailable in compaction-off mode; todowrite disabled)" : todowriteEnabled ? "registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, todowrite, ctx_reduce; registered /todos" : "registered tools: ctx_search, ctx_memory, ctx_note, ctx_expand, ctx_reduce (todowrite disabled)");
28801
28933
  pi.on("session_start", async (event, ctx) => {
28802
28934
  await handlePiCloneSessionStart(event, ctx, {
28803
28935
  db,
@@ -28841,7 +28973,6 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28841
28973
  return {
28842
28974
  db,
28843
28975
  projectIdentity: current.projectIdentity,
28844
- resolveProject: resolveCurrentProject,
28845
28976
  protectedTags: current.config.protected_tags,
28846
28977
  executeThresholdPercentage: current.config.execute_threshold_percentage,
28847
28978
  historyBudgetPercentage: current.config.history_budget_percentage,
@@ -28858,7 +28989,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28858
28989
  info("registered /ctx-status");
28859
28990
  registerStatusLine(pi, { db, projectIdentity });
28860
28991
  info("registered magic-context status line");
28861
- registerCtxFlushCommand(pi, { db });
28992
+ registerCtxFlushCommand(pi, { db, compactionOff });
28862
28993
  info("registered /ctx-flush");
28863
28994
  registerCtxRecompCommand(pi, {
28864
28995
  db,
@@ -28871,6 +29002,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28871
29002
  language: bootProjectDeps.config.language,
28872
29003
  memoryEnabled: bootProjectDeps.config.memory.enabled,
28873
29004
  autoPromote: bootProjectDeps.config.memory.auto_promote,
29005
+ compactionOff,
28874
29006
  resolveRuntimeDeps: (ctx) => {
28875
29007
  const current = resolveCurrentProjectDeps(ctx);
28876
29008
  return {
@@ -28883,7 +29015,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28883
29015
  historianThinkingLevel: current.historianConfig?.thinkingLevel,
28884
29016
  language: current.config.language,
28885
29017
  memoryEnabled: current.config.memory.enabled,
28886
- autoPromote: current.config.memory.auto_promote
29018
+ autoPromote: current.config.memory.auto_promote,
29019
+ compactionOff
28887
29020
  };
28888
29021
  }
28889
29022
  });
@@ -28899,6 +29032,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28899
29032
  language: bootProjectDeps.config.language,
28900
29033
  memoryEnabled: bootProjectDeps.config.memory.enabled,
28901
29034
  autoPromote: bootProjectDeps.config.memory.auto_promote,
29035
+ compactionOff,
28902
29036
  userMemoriesEnabled: userMemoryCollectionEnabled(bootProjectDeps.config.dreamer),
28903
29037
  executeThresholdPercentage: bootProjectDeps.config.execute_threshold_percentage,
28904
29038
  executeThresholdTokens: bootProjectDeps.config.execute_threshold_tokens,
@@ -28915,6 +29049,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28915
29049
  language: current.config.language,
28916
29050
  memoryEnabled: current.config.memory.enabled,
28917
29051
  autoPromote: current.config.memory.auto_promote,
29052
+ compactionOff,
28918
29053
  userMemoriesEnabled: userMemoryCollectionEnabled(current.config.dreamer),
28919
29054
  executeThresholdPercentage: current.config.execute_threshold_percentage,
28920
29055
  executeThresholdTokens: current.config.execute_threshold_tokens
@@ -28932,7 +29067,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28932
29067
  historianThinkingLevel: bootProjectDeps.historianConfig?.thinkingLevel,
28933
29068
  language: bootProjectDeps.config.language,
28934
29069
  memoryEnabled: bootProjectDeps.config.memory.enabled,
29070
+ allowHomeProject: bootProjectDeps.config.allow_home_project,
28935
29071
  autoPromote: bootProjectDeps.config.memory.auto_promote,
29072
+ compactionOff,
28936
29073
  userMemoriesEnabled: userMemoryCollectionEnabled(bootProjectDeps.config.dreamer),
28937
29074
  resolveRuntimeDeps: (ctx) => {
28938
29075
  const current = resolveCurrentProjectDeps(ctx);
@@ -28946,7 +29083,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28946
29083
  historianThinkingLevel: current.historianConfig?.thinkingLevel,
28947
29084
  language: current.config.language,
28948
29085
  memoryEnabled: current.config.memory.enabled,
29086
+ allowHomeProject: current.config.allow_home_project,
28949
29087
  autoPromote: current.config.memory.auto_promote,
29088
+ compactionOff,
28950
29089
  userMemoriesEnabled: userMemoryCollectionEnabled(current.config.dreamer)
28951
29090
  };
28952
29091
  }
@@ -28956,7 +29095,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28956
29095
  db,
28957
29096
  projectDir,
28958
29097
  projectIdentity,
28959
- resolveProject: resolveCurrentProject,
29098
+ resolveProject: (ctx) => {
29099
+ const current = resolveCurrentProjectDeps(ctx);
29100
+ return {
29101
+ projectDir: current.projectDir,
29102
+ projectIdentity: current.projectIdentity
29103
+ };
29104
+ },
28960
29105
  dreamerEnabled: bootProjectDeps.dreamerEnabled,
28961
29106
  resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
28962
29107
  onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity)
@@ -28968,7 +29113,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28968
29113
  projectIdentity,
28969
29114
  memoryEnabled: bootProjectDeps.config.memory.enabled,
28970
29115
  resolveMemoryEnabled: (ctx) => resolveCurrentProjectDeps(ctx).config.memory.enabled,
28971
- resolveProject: resolveCurrentProject
29116
+ resolveProject: (ctx) => {
29117
+ const current = resolveCurrentProjectDeps(ctx);
29118
+ return {
29119
+ projectDir: current.projectDir,
29120
+ projectIdentity: current.projectIdentity
29121
+ };
29122
+ }
28972
29123
  });
28973
29124
  info("registered /ctx-embed");
28974
29125
  const dreamerConfig = bootProjectDeps.dreamerConfig;
@@ -29057,11 +29208,11 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
29057
29208
  try {
29058
29209
  const smForDrain = sm;
29059
29210
  const canDrain = typeof smForDrain.appendCompaction === "function" && typeof smForDrain.getBranch === "function";
29060
- if (canDrain && getPendingPiCompactionMarkerState(db, sessionId)) {
29211
+ if (!compactionOff && canDrain && getPendingPiCompactionMarkerState(db, sessionId)) {
29061
29212
  signalPiDeferredCompactionMarkerDrain(sessionId);
29062
29213
  }
29063
29214
  } catch {}
29064
- if (ctx.hasUI && effectiveProjectDeps.historianConfig?.model) {
29215
+ if (!compactionOff && ctx.hasUI && effectiveProjectDeps.historianConfig?.model) {
29065
29216
  maybeSendUpgradeReminder({
29066
29217
  client: null,
29067
29218
  db,
@@ -29089,7 +29240,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
29089
29240
  memoryEnabled: effectiveConfig.memory.enabled,
29090
29241
  includeGuidance: true,
29091
29242
  protectedTags: effectiveConfig.protected_tags,
29092
- ctxReduceCallable: true,
29243
+ ctxReduceCallable: !compactionOff,
29093
29244
  dreamerEnabled: effectiveProjectDeps.dreamerEnabled,
29094
29245
  temporalAwarenessEnabled: effectiveConfig.temporal_awareness ?? false,
29095
29246
  cavemanTextCompressionEnabled: effectiveConfig.caveman_text_compression?.enabled === true,
@@ -29134,7 +29285,7 @@ ${block}` : event.systemPrompt;
29134
29285
  const lastAssistant = Array.isArray(msgs) ? [...msgs].reverse().find((m) => m?.role === "assistant") : undefined;
29135
29286
  if (lastAssistant?.stopReason === "stop") {
29136
29287
  const sessionId = ctx.sessionManager?.getSessionId?.();
29137
- if (sessionId && db)
29288
+ if (sessionId && db && !compactionOff)
29138
29289
  maybeDeliverChannel2Pi(pi, db, sessionId);
29139
29290
  }
29140
29291
  } catch (err) {
@@ -29159,7 +29310,7 @@ ${block}` : event.systemPrompt;
29159
29310
  toolCallId
29160
29311
  });
29161
29312
  if (Array.isArray(todos) && todos.length > 0 && todos.every((t) => t.status === "completed" || t.status === "cancelled")) {
29162
- if (sessionMeta && !sessionMeta.isSubagent) {
29313
+ if (!compactionOff && sessionMeta && !sessionMeta.isSubagent) {
29163
29314
  onNoteTrigger(db, sessionId, "todos_complete");
29164
29315
  }
29165
29316
  }
@@ -29175,7 +29326,7 @@ ${block}` : event.systemPrompt;
29175
29326
  const sessionId = ctx.sessionManager.getSessionId();
29176
29327
  if (typeof sessionId !== "string" || sessionId.length === 0)
29177
29328
  return;
29178
- if (event.toolName === "ctx_reduce") {
29329
+ if (!compactionOff && event.toolName === "ctx_reduce") {
29179
29330
  markPiChannel1Reduced(sessionId, db);
29180
29331
  }
29181
29332
  } catch (err) {
@@ -29187,6 +29338,8 @@ ${block}` : event.systemPrompt;
29187
29338
  const sessionId = ctx.sessionManager.getSessionId();
29188
29339
  if (typeof sessionId !== "string" || sessionId.length === 0)
29189
29340
  return;
29341
+ if (compactionOff)
29342
+ return;
29190
29343
  if (db)
29191
29344
  maybeDeliverChannel2Pi(pi, db, sessionId, "steer");
29192
29345
  const block = maybeChannel1ReminderForToolResult({
@@ -29202,20 +29355,11 @@ ${block}` : event.systemPrompt;
29202
29355
  log(`tool_result hook failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
29203
29356
  }
29204
29357
  });
29205
- pi.on("session_before_compact", async (_event, ctx) => {
29206
- try {
29207
- const sessionId = ctx.sessionManager?.getSessionId?.();
29208
- if (typeof sessionId === "string" && sessionId.length > 0) {
29209
- clearPiM0Cache(db, sessionId, "session_before_compact");
29210
- }
29211
- } catch {}
29212
- info("session_before_compact: cancelling — magic-context owns compaction");
29213
- return { cancel: true };
29214
- });
29358
+ pi.on("session_before_compact", async (_event, ctx) => handlePiSessionBeforeCompact({ db, compactionOff, ctx }));
29215
29359
  pi.on("message_end", async (event, ctx) => {
29216
29360
  try {
29217
29361
  const msg = event.message;
29218
- if (msg !== null && typeof msg === "object") {
29362
+ if (!compactionOff && msg !== null && typeof msg === "object") {
29219
29363
  stripTagPrefixFromAssistantMessage(msg);
29220
29364
  }
29221
29365
  } catch (err) {
@@ -29247,6 +29391,7 @@ ${block}` : event.systemPrompt;
29247
29391
  sessionId,
29248
29392
  message: event.message,
29249
29393
  piContextWindow,
29394
+ piModel: ctx.model,
29250
29395
  piTokens: piUsage && typeof piUsage.tokens === "number" ? piUsage.tokens : undefined,
29251
29396
  notifyIssue: async (message) => {
29252
29397
  const uiNotify = ctx.ui?.notify;
@@ -29276,6 +29421,8 @@ ${block}` : event.systemPrompt;
29276
29421
  warn("message_end: persist session_meta usage failed:", err);
29277
29422
  }
29278
29423
  try {
29424
+ if (compactionOff)
29425
+ return;
29279
29426
  const sm = ctx.sessionManager;
29280
29427
  const sessionId = sm?.getSessionId?.();
29281
29428
  if (typeof sessionId !== "string" || sessionId.length === 0)
@@ -29293,8 +29440,8 @@ ${block}` : event.systemPrompt;
29293
29440
  if (!detection.isOverflow)
29294
29441
  return;
29295
29442
  const modelKey = typeof msg.provider === "string" && typeof msg.model === "string" && msg.provider.length > 0 && msg.model.length > 0 ? `${msg.provider}/${msg.model}` : undefined;
29296
- recordOverflowDetected(db, sessionId, detection.reportedLimit, modelKey);
29297
- log(`[magic-context][${sessionId}] overflow detected: reportedLimit=${detection.reportedLimit ?? "?"} pattern=${detection.matchedPattern ?? "?"}`);
29443
+ recordOverflowDetected(db, sessionId, detection.reportedLimit, modelKey, "provider_overflow", detection.reportedLimitProvenance);
29444
+ log(`[magic-context][${sessionId}] overflow detected: reportedLimit=${detection.reportedLimit ?? "?"} provenance=${detection.reportedLimitProvenance ?? "?"} pattern=${detection.matchedPattern ?? "?"}`);
29298
29445
  } catch (err) {
29299
29446
  warn("message_end: overflow detection failed:", err);
29300
29447
  }
@@ -29331,6 +29478,7 @@ ${block}` : event.systemPrompt;
29331
29478
  clearContextHandlerSession(sessionId);
29332
29479
  }
29333
29480
  } catch {}
29481
+ clearPiMagicContextActive();
29334
29482
  });
29335
29483
  pi.on("session_before_switch", (_event, ctx) => {
29336
29484
  try {
@@ -29359,6 +29507,7 @@ export {
29359
29507
  resolveDreamerFromConfig,
29360
29508
  persistPiPressureFromMessageEnd,
29361
29509
  persistPiMessageEndModelMeta,
29510
+ handlePiSessionBeforeCompact,
29362
29511
  src_default as default,
29363
29512
  capturePiTodowriteMessageIfCompatible,
29364
29513
  capturePiTodowriteArgsIfCompatible,