@cortexkit/pi-magic-context 0.33.1 → 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,5 +1,7 @@
1
1
  import {
2
2
  AGE_RECLAIM_MIN_TOKENS,
3
+ BoundedSessionMap,
4
+ COMPACTION_ENABLED_PATH,
3
5
  COMPARTMENT_LEASE_RENEWAL_MS,
4
6
  COMPARTMENT_RENDER_EPOCH,
5
7
  DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE,
@@ -10,6 +12,8 @@ import {
10
12
  Database,
11
13
  GIT_SWEEP_LEASE_RENEWAL_MS,
12
14
  GLOBAL_USER_PROFILE_PROJECT_PATH,
15
+ LATEST_SUPPORTED_VERSION,
16
+ MAX_EXECUTE_THRESHOLD,
13
17
  PRIMER_CANDIDATE_MAX_AGE_MS,
14
18
  PRIMER_CANDIDATE_TTL_MS,
15
19
  ProjectIdentityError,
@@ -57,6 +61,7 @@ import {
57
61
  clearInjectionCache,
58
62
  clearMemoryVerifications,
59
63
  clearPendingCompactionMarkerStateIf,
64
+ clearPendingOps,
60
65
  clearPendingPiCompactionMarkerStateIf,
61
66
  clearPersistedReasoningWatermark,
62
67
  clearPersistedTodoSyntheticAnchor,
@@ -89,6 +94,7 @@ import {
89
94
  encodeCachedM0UpgradeIdentity,
90
95
  enqueueShadowEmbeddingItems,
91
96
  ensureProjectRegisteredFromPiDirectory,
97
+ escalationBands,
92
98
  escapeXmlAttr,
93
99
  escapeXmlContent,
94
100
  estimateTokens,
@@ -109,6 +115,7 @@ import {
109
115
  getAutoSearchHintDecisions,
110
116
  getCachedAbsoluteMessageCount,
111
117
  getChannel2NudgeState,
118
+ getCompactionModeRecord,
112
119
  getCompartments,
113
120
  getCompartmentsByEndMessageId,
114
121
  getContextStoreUuid,
@@ -141,6 +148,7 @@ import {
141
148
  getMemoryVerifications,
142
149
  getMessageIndexReconciliationStartOrdinal,
143
150
  getMessageIndexSourceIdentity,
151
+ getMigrationOnOpenRefusal,
144
152
  getModuleNoteEvaluationBridge,
145
153
  getMuralCueState,
146
154
  getNoteLastReadAt,
@@ -156,6 +164,7 @@ import {
156
164
  getPendingPiCompactionMarkerState,
157
165
  getPendingSmartNotes,
158
166
  getPersistedNoteNudge,
167
+ getPersistedSchemaVersion,
159
168
  getPersistedTodoSyntheticAnchor,
160
169
  getPersistedToolTagAccounting,
161
170
  getPrimerCandidatesByIds,
@@ -203,11 +212,13 @@ import {
203
212
  insertUserMemory,
204
213
  insertUserMemoryCandidates,
205
214
  invalidateMemory,
215
+ isCompactionEnabled,
206
216
  isCompartmentLeaseHeld,
207
217
  isDreamerRunnable,
208
218
  isEditTool,
209
219
  isMessageIndexReconciledThrough,
210
220
  isMessageIndexSourceCurrent,
221
+ isMidTurn,
211
222
  isMidTurnPi,
212
223
  isRecord,
213
224
  isSaneLimit,
@@ -282,7 +293,9 @@ import {
282
293
  reserveProtectedTailDrainTokens,
283
294
  resetLastNudgeCycle,
284
295
  resetLastNudgeCycleIfTailShrank,
296
+ resolveCompactionModeRecord,
285
297
  resolveGitTopLevel,
298
+ resolveLimit,
286
299
  resolveModelRefForPi,
287
300
  resolveMuralWire,
288
301
  resolvePiStableId,
@@ -298,6 +311,7 @@ import {
298
311
  saveSourceContent,
299
312
  scheduleAfterBootQuiet,
300
313
  setChannel2NudgeState,
314
+ setCompactionModeRecord,
301
315
  setDeferredExecutePendingIfAbsent,
302
316
  setEmergencyDropSample,
303
317
  setLastNudgeLevel,
@@ -312,6 +326,7 @@ import {
312
326
  setRecompPartialRange,
313
327
  setSessionWorkMetrics,
314
328
  setSqlitePragmaConfig,
329
+ setStoragePrivatePermissionEnforcement,
315
330
  setTodoSnapshot,
316
331
  sourceNameForMemory,
317
332
  stableStringify,
@@ -346,7 +361,10 @@ import {
346
361
  withMigrationLanguageDirective,
347
362
  withRawMessageProvider,
348
363
  withRawSessionMessageCache
349
- } from "./index-e3kftg52.js";
364
+ } from "./index-0k3j1tc3.js";
365
+ import {
366
+ pushNotification
367
+ } from "./index-hw34xmzk.js";
350
368
  import {
351
369
  ensureCortexKitArtifactGitignore,
352
370
  getHarness,
@@ -528,14 +546,41 @@ function isMagicContextHiddenAgentName(agent) {
528
546
  return true;
529
547
  return false;
530
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
+ }
531
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
+ }
532
581
  if (reason.kind === "schema_fence") {
533
582
  return [
534
- `Magic Context cannot operate: shared database schema v${reason.persistedVersion}`,
535
- `is newer than this build supports (max v${reason.supportedVersion}).`,
536
- "A newer OpenCode/Pi instance upgraded the database; this build fail-closed",
537
- "so it cannot corrupt the cache or silently fall back to native compaction.",
538
- `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}).`,
539
584
  `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
540
585
  ].join(" ");
541
586
  }
@@ -596,7 +641,7 @@ function createFailClosedController(options) {
596
641
  const shouldReprobe = typeof input.tryReopen === "function" && (blockedPassCount === 1 || blockedPassCount % reprobeEveryN === 0);
597
642
  if (shouldReprobe) {
598
643
  try {
599
- const healed = await input.tryReopen();
644
+ const healed = await input.tryReopen?.();
600
645
  if (healed) {
601
646
  reason = null;
602
647
  blockedPassCount = 0;
@@ -779,6 +824,7 @@ var OVERFLOW_PATTERNS = [
779
824
  /maximum prompt length is \d+/i,
780
825
  /reduce the length of the messages/i,
781
826
  /maximum context length is \d+ tokens/i,
827
+ /maximum model length is \d+/i,
782
828
  /exceeds the limit of \d+/i,
783
829
  /exceeds the available context size/i,
784
830
  /greater than the context length/i,
@@ -794,15 +840,25 @@ var OVERFLOW_PATTERNS = [
794
840
  /context size has been exceeded/i
795
841
  ];
796
842
  var LIMIT_EXTRACTION_PATTERNS = [
797
- /maximum prompt length is (\d+)/i,
798
- /maximum context length is (\d+) tokens?/i,
799
- /context length is only (\d+) tokens?/i,
800
- /exceeds the limit of (\d+)/i,
801
- /too large for model with (\d+) maximum context length/i,
802
- /context size.*(\d+) tokens?/i,
803
- /exceeds? the context length of (\d+)/i,
804
- />\s*(\d+)\s*(?:tokens?\s*)?(?:maximum|max|limit)\b/i,
805
- /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" }
806
862
  ];
807
863
  var MIN_PLAUSIBLE_LIMIT = 1024;
808
864
  var MAX_PLAUSIBLE_LIMIT = 1e7;
@@ -853,14 +909,15 @@ function detectOverflow(error) {
853
909
  const reportedLimit = parseReportedLimit(message);
854
910
  return {
855
911
  isOverflow: true,
856
- reportedLimit,
912
+ reportedLimit: reportedLimit?.value,
913
+ reportedLimitProvenance: reportedLimit?.provenance,
857
914
  matchedPattern: matched?.source
858
915
  };
859
916
  }
860
917
  function parseReportedLimit(message) {
861
918
  if (!message)
862
919
  return;
863
- for (const pattern of LIMIT_EXTRACTION_PATTERNS) {
920
+ for (const { pattern, provenance } of LIMIT_EXTRACTION_PATTERNS) {
864
921
  const match = message.match(pattern);
865
922
  if (!match)
866
923
  continue;
@@ -872,7 +929,7 @@ function parseReportedLimit(message) {
872
929
  continue;
873
930
  if (value < MIN_PLAUSIBLE_LIMIT || value > MAX_PLAUSIBLE_LIMIT)
874
931
  continue;
875
- return value;
932
+ return { value, provenance };
876
933
  }
877
934
  return;
878
935
  }
@@ -1354,6 +1411,15 @@ async function runDeferredV22Backfill(db, options = {}) {
1354
1411
  };
1355
1412
  }
1356
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
+
1357
1423
  // ../plugin/src/hooks/magic-context/derive-budgets.ts
1358
1424
  var TRIGGER_BUDGET_PERCENTAGE = 0.05;
1359
1425
  var TRIGGER_BUDGET_MIN = 5000;
@@ -1397,7 +1463,6 @@ function resolveHistorianContextLimit(historianModelOverride) {
1397
1463
 
1398
1464
  // ../plugin/src/hooks/magic-context/event-resolvers.ts
1399
1465
  var DEFAULT_CONTEXT_LIMIT = 128000;
1400
- var MAX_EXECUTE_THRESHOLD = 80;
1401
1466
  function resolveCacheTtl(cacheTtl, modelKey) {
1402
1467
  if (typeof cacheTtl === "string") {
1403
1468
  return cacheTtl;
@@ -1442,7 +1507,7 @@ function resolveExecuteThresholdDetail(config, modelKey, fallback, options) {
1442
1507
  const dedupeKey = `${options.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${tokenMatch.value}|${cap}`;
1443
1508
  if (!clampWarnSeen.has(dedupeKey)) {
1444
1509
  clampWarnSeen.add(dedupeKey);
1445
- 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"}`;
1446
1511
  if (options.sessionId) {
1447
1512
  sessionLog(options.sessionId, `WARN: ${msg}`);
1448
1513
  } else {
@@ -1498,7 +1563,7 @@ function resolveExecuteThresholdDetail(config, modelKey, fallback, options) {
1498
1563
  const dedupeKey = `pct|${options?.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${resolved}`;
1499
1564
  if (!clampWarnSeen.has(dedupeKey)) {
1500
1565
  clampWarnSeen.add(dedupeKey);
1501
- 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)`;
1502
1567
  if (options?.sessionId) {
1503
1568
  sessionLog(options.sessionId, `WARN: ${msg}`);
1504
1569
  } else {
@@ -1990,6 +2055,57 @@ import {
1990
2055
  import { createInterface } from "node:readline";
1991
2056
  import { fileURLToPath } from "node:url";
1992
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
+
1993
2109
  // ../plugin/src/features/magic-context/subagent-token-capture.ts
1994
2110
  function asNumber(value) {
1995
2111
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
@@ -2338,6 +2454,10 @@ class PiSubagentRunner {
2338
2454
  if (ZERO_TOOL_PROMPT_REQUIRED_AGENTS.has(options.agent) && options.systemPrompt.trim().length === 0) {
2339
2455
  return failBeforeSpawn("invalid_prompt", `zero-tool Pi subagent "${options.agent}" requires a non-empty system prompt`, true);
2340
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
+ }
2341
2461
  const promptBytes = Buffer.byteLength(options.userMessage, "utf8");
2342
2462
  const deliverViaStdin = promptBytes > PROMPT_ARGV_MAX_BYTES || this.platform === "win32";
2343
2463
  let systemPromptTempDir;
@@ -2909,7 +3029,7 @@ function registerCtxAugCommand(pi, config) {
2909
3029
  sessionLog(sessionLabel, "/ctx-aug: spawning sidekick", {
2910
3030
  model: currentConfig.model
2911
3031
  });
2912
- const projectIdentity = resolveProjectIdentityForSession(ctx.cwd);
3032
+ const projectIdentity = resolveProjectIdentityForSession(ctx.cwd, currentConfig.allowHomeProject);
2913
3033
  if (!projectIdentity) {
2914
3034
  sessionLog(sessionLabel, "Error: Could not resolve project identity for sidekick.");
2915
3035
  return;
@@ -2982,6 +3102,201 @@ var DREAMER_MEMORY_MAPPER_AGENT = "dreamer-memory-mapper";
2982
3102
  var DREAMER_CLASSIFIER_AGENT = "dreamer-classifier";
2983
3103
  var DREAMER_DOCS_AGENT = "dreamer-docs";
2984
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
+ }
2985
3300
  // ../plugin/src/shared/model-suggestion-retry.ts
2986
3301
  var ABORT_CALL_TIMEOUT_MS = 3000;
2987
3302
  function copyPromptArgs(args, body) {
@@ -3635,7 +3950,7 @@ function parseCuesManifest(text) {
3635
3950
  const body = extractCompleteManifestBody(text, "cues");
3636
3951
  const out = [];
3637
3952
  for (const match of body.matchAll(/<cue\s+id="(\d+)"\s*>([\s\S]*?)<\/cue>/g)) {
3638
- const id = Number.parseInt(match[1], 10);
3953
+ const id = Number.parseInt(match[1] ?? "", 10);
3639
3954
  if (!Number.isInteger(id))
3640
3955
  continue;
3641
3956
  out.push({ id, cue: unescapeXml(match[2] ?? "").trim() });
@@ -3736,7 +4051,10 @@ async function runCompressCues(args) {
3736
4051
  break;
3737
4052
  }
3738
4053
  const sliceMs = computeChunkSliceMs(remainingMs, chunks.length - i);
3739
- 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);
3740
4058
  result.compressed += outcome.compressed;
3741
4059
  result.skipped += outcome.skipped;
3742
4060
  result.remaining -= outcome.compressed;
@@ -3771,12 +4089,12 @@ async function compressOneChunk(args, chunk, sliceMs, signal) {
3771
4089
  projectPath: args.projectIdentity,
3772
4090
  memories: chunk.map(toPromptMemory)
3773
4091
  });
3774
- const createResponse = await args.client.session.create({
3775
- body: {
3776
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
3777
- title: "magic-context-dream-compress-cues"
3778
- },
3779
- 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
3780
4098
  });
3781
4099
  const created = normalizeSDKResponse(createResponse, null, {
3782
4100
  preferResponseOnMissingData: true
@@ -4260,12 +4578,12 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
4260
4578
  abortController.abort();
4261
4579
  });
4262
4580
  try {
4263
- const createResponse = await args.client.session.create({
4264
- body: {
4265
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
4266
- title: "magic-context-dream-user-memories"
4267
- },
4268
- 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
4269
4587
  });
4270
4588
  const created = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
4271
4589
  agentSessionId = typeof created?.id === "string" ? created.id : null;
@@ -4859,12 +5177,12 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
4859
5177
  recordInvocation(args, startedAt, { status: "completed" });
4860
5178
  return run2;
4861
5179
  }
4862
- const createResponse = await args.client.session.create({
4863
- body: {
4864
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
4865
- title: "magic-context-dream-classify"
4866
- },
4867
- 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
4868
5186
  });
4869
5187
  const created = normalizeSDKResponse(createResponse, null, {
4870
5188
  preferResponseOnMissingData: true
@@ -5676,7 +5994,7 @@ function getAsyncModule() {
5676
5994
  asyncModulePromise ??= (async () => {
5677
5995
  const [{ default: singlefileAsyncifyVariant }, { newQuickJSAsyncWASMModuleFromVariant }] = await Promise.all([
5678
5996
  import("./index-ayd60xnw.js").then((m)=>__toESM(m.default,1)),
5679
- import("./index-v7fmc1s2.js")
5997
+ import("./index-3pnd4qh9.js")
5680
5998
  ]);
5681
5999
  return newQuickJSAsyncWASMModuleFromVariant(singlefileAsyncifyVariant);
5682
6000
  })();
@@ -5895,12 +6213,12 @@ Remember: output only the JSON object described by the system prompt.`;
5895
6213
  });
5896
6214
  };
5897
6215
  try {
5898
- const createResponse = await args.client.session.create({
5899
- body: {
5900
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
5901
- title: `magic-context-smart-note-compile-${args.note.id}`
5902
- },
5903
- 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
5904
6222
  });
5905
6223
  const created = normalizeSDKResponse(createResponse, null, {
5906
6224
  preferResponseOnMissingData: true
@@ -6681,12 +6999,12 @@ async function confirmReadOnly(args, noteId, content, surfaceCondition, leaseSig
6681
6999
  });
6682
7000
  };
6683
7001
  try {
6684
- const createResponse = await args.client.session.create({
6685
- body: {
6686
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
6687
- title: `magic-context-smart-note-confirm-${noteId}`
6688
- },
6689
- 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
6690
7008
  });
6691
7009
  const created = normalizeSDKResponse(createResponse, null, {
6692
7010
  preferResponseOnMissingData: true
@@ -7031,12 +7349,12 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
7031
7349
  let agentSessionId = null;
7032
7350
  const startedAt = Date.now();
7033
7351
  try {
7034
- const createResponse = await args.client.session.create({
7035
- body: {
7036
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
7037
- title: "magic-context-dream-map-memories"
7038
- },
7039
- 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
7040
7358
  });
7041
7359
  const created = normalizeSDKResponse(createResponse, null, {
7042
7360
  preferResponseOnMissingData: true
@@ -7648,12 +7966,12 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
7648
7966
  let agentSessionId = null;
7649
7967
  const startedAt = Date.now();
7650
7968
  try {
7651
- const createResponse = await args.client.session.create({
7652
- body: {
7653
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
7654
- title: "magic-context-dream-refresh-primers"
7655
- },
7656
- 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
7657
7975
  });
7658
7976
  const created = normalizeSDKResponse(createResponse, null, {
7659
7977
  preferResponseOnMissingData: true
@@ -8151,8 +8469,8 @@ function extractToolRows(parts) {
8151
8469
  const record = part;
8152
8470
  if (record.type !== "tool" || typeof record.tool !== "string")
8153
8471
  continue;
8154
- const state = record.state;
8155
- const stateRecord = state && typeof state === "object" ? state : {};
8472
+ const state2 = record.state;
8473
+ const stateRecord = state2 && typeof state2 === "object" ? state2 : {};
8156
8474
  const output = stringifyToolOutput(stateRecord.output);
8157
8475
  const errorText = stringifyToolOutput(stateRecord.error);
8158
8476
  const status = typeof stateRecord.status === "string" ? stateRecord.status : "";
@@ -8479,12 +8797,12 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
8479
8797
  let agentSessionId = null;
8480
8798
  const startedAt = Date.now();
8481
8799
  try {
8482
- const createResponse = await args.client.session.create({
8483
- body: {
8484
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
8485
- title: "magic-context-dream-verify"
8486
- },
8487
- 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
8488
8806
  });
8489
8807
  const created = normalizeSDKResponse(createResponse, null, {
8490
8808
  preferResponseOnMissingData: true
@@ -9171,12 +9489,12 @@ async function runRetrospectiveTask(config, ctx, helpers) {
9171
9489
  });
9172
9490
  let childSessionId = null;
9173
9491
  try {
9174
- const createResponse = await deps.client.session.create({
9175
- body: {
9176
- ...parent ? { parentID: parent } : {},
9177
- title: "magic-context-dream-retrospective"
9178
- },
9179
- 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
9180
9498
  });
9181
9499
  const created = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
9182
9500
  childSessionId = typeof created?.id === "string" ? created.id : null;
@@ -9361,12 +9679,12 @@ async function runAgenticTask(config, ctx, helpers) {
9361
9679
  });
9362
9680
  let childSessionId = null;
9363
9681
  try {
9364
- const createResponse = await deps.client.session.create({
9365
- body: {
9366
- ...parent ? { parentID: parent } : {},
9367
- title: `magic-context-dream-${task}`
9368
- },
9369
- 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
9370
9688
  });
9371
9689
  const created = normalizeSDKResponse(createResponse, null, {
9372
9690
  preferResponseOnMissingData: true
@@ -9538,11 +9856,11 @@ function planDueTasks(db, projectIdentity, tasks, now) {
9538
9856
  const due = [];
9539
9857
  for (const config of tasks) {
9540
9858
  reconcileSchedule(db, projectIdentity, config, now);
9541
- const state = getTaskScheduleState(db, projectIdentity, config.task);
9542
- if (!state || state.nextDueAt === null)
9859
+ const state2 = getTaskScheduleState(db, projectIdentity, config.task);
9860
+ if (!state2 || state2.nextDueAt === null)
9543
9861
  continue;
9544
- if (now >= state.nextDueAt) {
9545
- due.push({ config, scheduledAt: state.nextDueAt });
9862
+ if (now >= state2.nextDueAt) {
9863
+ due.push({ config, scheduledAt: state2.nextDueAt });
9546
9864
  }
9547
9865
  }
9548
9866
  return due;
@@ -11047,6 +11365,101 @@ function executeFlush(db, sessionId) {
11047
11365
  }
11048
11366
  }
11049
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
+
11050
11463
  // src/context-handler.ts
11051
11464
  import * as crypto3 from "node:crypto";
11052
11465
 
@@ -11060,6 +11473,9 @@ var UNIT_TO_MS = {
11060
11473
  };
11061
11474
  function parseCacheTtl(ttl) {
11062
11475
  const normalizedTtl = ttl.trim();
11476
+ if (normalizedTtl.toLowerCase() === "never") {
11477
+ return Number.POSITIVE_INFINITY;
11478
+ }
11063
11479
  if (NUMERIC_PATTERN.test(normalizedTtl)) {
11064
11480
  return Number(normalizedTtl);
11065
11481
  }
@@ -11775,9 +12191,9 @@ function applyFlushedStatuses(sessionId, db, targets, preloadedTags) {
11775
12191
  }
11776
12192
 
11777
12193
  // ../plugin/src/hooks/magic-context/boundary-execution.ts
11778
- var FORCE_MATERIALIZE_PERCENTAGE = 85;
11779
12194
  function detectMidTurnBypassReason(input) {
11780
- if (input.contextUsage.percentage >= FORCE_MATERIALIZE_PERCENTAGE)
12195
+ const { forceMaterializationPercentage } = escalationBands(input.effectiveExecuteThresholdPercentage);
12196
+ if (input.contextUsage.percentage >= forceMaterializationPercentage)
11781
12197
  return "force-materialize";
11782
12198
  if (input.historyRefreshSessions.has(input.sessionId))
11783
12199
  return "explicit-bust";
@@ -12270,29 +12686,29 @@ function callIdFromPart(part) {
12270
12686
  const direct = firstStringField(part, ["callID", "callId", "toolCallId", "tool_call_id", "id"]);
12271
12687
  if (direct)
12272
12688
  return direct;
12273
- const state = isRecord(part.state) ? part.state : null;
12274
- 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"]) ?? "" : "";
12275
12691
  }
12276
12692
  function toolSignalFromPart(part) {
12277
12693
  if (!isRecord(part))
12278
12694
  return null;
12279
12695
  const type = partType(part);
12280
- const state = isRecord(part.state) ? part.state : null;
12696
+ const state2 = isRecord(part.state) ? part.state : null;
12281
12697
  const callId = callIdFromPart(part);
12282
12698
  if (!callId && type !== "tool")
12283
12699
  return null;
12284
12700
  if (type === "tool") {
12285
- const hasInput = state !== null && hasOwn(state, "input");
12286
- 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;
12287
12703
  const hasOutput = outputKey !== null;
12288
- const outputValue = outputKey && state ? state[outputKey] : undefined;
12704
+ const outputValue = outputKey && state2 ? state2[outputKey] : undefined;
12289
12705
  const providerExecuted = part.providerExecuted === true;
12290
12706
  const openInvocation = !providerExecuted && !hasOutput;
12291
12707
  return {
12292
12708
  callId,
12293
12709
  hasInput: hasInput || openInvocation,
12294
12710
  hasOutput,
12295
- inputText: hasInput && state ? stringValue(state.input) : "",
12711
+ inputText: hasInput && state2 ? stringValue(state2.input) : "",
12296
12712
  outputText: hasOutput ? stringValue(outputValue) : ""
12297
12713
  };
12298
12714
  }
@@ -12520,24 +12936,27 @@ function tokenForMessage(message, options) {
12520
12936
  function buildTrueRawTokenIndex(sessionId, messages, options) {
12521
12937
  const ordered = [...messages].sort((a, b) => a.ordinal - b.ordinal);
12522
12938
  const sliceCount = ordered.length;
12523
- const maxOrdinal = ordered.length > 0 ? ordered[ordered.length - 1].ordinal : 0;
12524
- 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;
12525
12943
  const tokensByOrdinal = new Map;
12526
12944
  const idsByOrdinal = new Map;
12527
- const prefix = new Array(rawMessageCount + 1).fill(0);
12945
+ const prefix = new Array(ordinalSpan + 1).fill(0);
12528
12946
  for (const message of ordered) {
12529
12947
  const stored = options.storedTotalForMessage?.(message);
12530
12948
  const total = stored !== undefined && stored !== null ? stored : tokenForMessage(message, options).total;
12531
12949
  tokensByOrdinal.set(message.ordinal, total);
12532
12950
  idsByOrdinal.set(message.ordinal, message.id);
12533
- if (message.ordinal >= 1 && message.ordinal <= rawMessageCount) {
12534
- prefix[message.ordinal] = total;
12951
+ const relative = message.ordinal - firstOrdinal + 1;
12952
+ if (relative >= 1 && relative <= ordinalSpan) {
12953
+ prefix[relative] = total;
12535
12954
  }
12536
12955
  }
12537
- for (let k = 1;k <= rawMessageCount; k += 1) {
12956
+ for (let k = 1;k <= ordinalSpan; k += 1) {
12538
12957
  prefix[k] += prefix[k - 1];
12539
12958
  }
12540
- const ordinalToIndex = (ordinal) => Math.max(0, Math.min(rawMessageCount, ordinal - 1));
12959
+ const ordinalToIndex = (ordinal) => Math.max(0, Math.min(ordinalSpan, ordinal - firstOrdinal));
12541
12960
  return {
12542
12961
  sessionId,
12543
12962
  providerShapeVersion: options.providerShapeVersion,
@@ -12549,27 +12968,27 @@ function buildTrueRawTokenIndex(sessionId, messages, options) {
12549
12968
  return idsByOrdinal.get(ordinal) ?? null;
12550
12969
  },
12551
12970
  suffixTokensFromOrdinal(ordinal) {
12552
- if (ordinal <= 1)
12553
- return prefix[rawMessageCount];
12554
- if (ordinal > rawMessageCount)
12971
+ if (ordinal <= firstOrdinal)
12972
+ return prefix[ordinalSpan];
12973
+ if (ordinal > terminalOrdinal)
12555
12974
  return 0;
12556
- return prefix[rawMessageCount] - prefix[ordinalToIndex(ordinal)];
12975
+ return prefix[ordinalSpan] - prefix[ordinalToIndex(ordinal)];
12557
12976
  },
12558
12977
  rangeTokens(startInclusive, endExclusive) {
12559
- const start = Math.max(1, startInclusive);
12560
- const end = Math.max(start, Math.min(rawMessageCount + 1, endExclusive));
12561
- 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];
12562
12981
  },
12563
12982
  findSuffixStartForTokens(tokens) {
12564
12983
  if (!Number.isFinite(tokens) || tokens <= 0)
12565
- return rawMessageCount + 1;
12984
+ return terminalOrdinal + 1;
12566
12985
  const target = Math.max(0, Math.floor(tokens));
12567
- const total = prefix[rawMessageCount];
12986
+ const total = prefix[ordinalSpan];
12568
12987
  if (total < target)
12569
- return 1;
12988
+ return firstOrdinal;
12570
12989
  const cut = total - target;
12571
12990
  let lo = 0;
12572
- let hi = rawMessageCount;
12991
+ let hi = ordinalSpan;
12573
12992
  let best = 0;
12574
12993
  while (lo <= hi) {
12575
12994
  const mid = lo + hi >> 1;
@@ -12580,29 +12999,31 @@ function buildTrueRawTokenIndex(sessionId, messages, options) {
12580
12999
  hi = mid - 1;
12581
13000
  }
12582
13001
  }
12583
- return best + 1;
13002
+ return firstOrdinal + best;
12584
13003
  },
12585
13004
  findHeadEndForCap(startInclusive, endExclusive, capTokens) {
12586
- const start = Math.max(1, Math.min(rawMessageCount + 1, startInclusive));
12587
- 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));
12588
13007
  if (!Number.isFinite(capTokens) || capTokens <= 0)
12589
13008
  return start;
12590
- const startPrefix = prefix[start - 1];
12591
- const cut = startPrefix + Math.floor(capTokens);
12592
- let lo = start;
12593
- let hi = end - 1;
12594
- 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;
12595
13015
  while (lo <= hi) {
12596
13016
  const mid = lo + hi >> 1;
12597
13017
  if (prefix[mid] <= cut) {
12598
- bestEnd = mid + 1;
13018
+ bestEndIndex = mid;
12599
13019
  lo = mid + 1;
12600
13020
  } else {
12601
13021
  hi = mid - 1;
12602
13022
  }
12603
13023
  }
13024
+ let bestEnd = firstOrdinal + bestEndIndex;
12604
13025
  if (bestEnd === start && start < end)
12605
- return start + 1;
13026
+ bestEnd = start + 1;
12606
13027
  return Math.min(bestEnd, end);
12607
13028
  }
12608
13029
  };
@@ -12918,7 +13339,8 @@ function resolveProtectedTailBoundary(ctx) {
12918
13339
  if (ctx.migrationFloorActive)
12919
13340
  runtimeFloor = Math.max(runtimeFloor, ctx.priorBoundaryOrdinal);
12920
13341
  let protectedTailStart = Math.max(boundary, runtimeFloor);
12921
- if (!ctx.emergencyTailScale && usagePercentage < 80) {
13342
+ const forceMaterializationPercentage = escalationBands(ctx.executeThresholdPercentage).forceMaterializationPercentage;
13343
+ if (!ctx.emergencyTailScale && usagePercentage < forceMaterializationPercentage) {
12922
13344
  let lastMeaningfulUserOrdinal = 0;
12923
13345
  for (let i = messages.length - 1;i >= 0; i--) {
12924
13346
  const message = messages[i];
@@ -13159,7 +13581,8 @@ function getRawHistoryEligibility(db, sessionId) {
13159
13581
  function hasRunnableCompartmentWindow(snapshot) {
13160
13582
  if (snapshot.offset >= snapshot.protectedTailStart)
13161
13583
  return false;
13162
- if (snapshot.usagePercentage >= 80 || snapshot.emergencyTailScale) {
13584
+ const forceMaterializationPercentage = escalationBands(snapshot.executeThresholdPercentage).forceMaterializationPercentage;
13585
+ if (snapshot.usagePercentage >= forceMaterializationPercentage || snapshot.emergencyTailScale) {
13163
13586
  return snapshot.trueRawEligibleTokens >= deriveMinForceEligibleTokens(snapshot.N) || snapshot.eligibleEndOrdinal > snapshot.offset;
13164
13587
  }
13165
13588
  return snapshot.eligibleEndOrdinal > snapshot.offset;
@@ -13222,11 +13645,18 @@ function validateBoundarySnapshot(args) {
13222
13645
  return { ok: true };
13223
13646
  }
13224
13647
  function recordHighPressureNoEligibleHead(db, snapshot) {
13225
- if (snapshot.usagePercentage < 80 && !snapshot.emergencyTailScale)
13648
+ const forceMaterializationPercentage = escalationBands(snapshot.executeThresholdPercentage).forceMaterializationPercentage;
13649
+ if (snapshot.usagePercentage < forceMaterializationPercentage && !snapshot.emergencyTailScale) {
13226
13650
  return 0;
13651
+ }
13227
13652
  return recordProtectedTailNoEligibleHead(db, snapshot.sessionId);
13228
13653
  }
13229
13654
 
13655
+ // ../plugin/src/hooks/magic-context/sentinel.ts
13656
+ function modelAcceptsEmptyContent(providerID) {
13657
+ return providerID === "anthropic";
13658
+ }
13659
+
13230
13660
  // ../plugin/src/hooks/magic-context/compartment-trigger.ts
13231
13661
  var PROACTIVE_TRIGGER_OFFSET_PERCENTAGE = 2;
13232
13662
  var POST_DROP_TARGET_RATIO = 0.75;
@@ -13234,7 +13664,6 @@ var MIN_PROACTIVE_TAIL_TOKEN_ESTIMATE = 6000;
13234
13664
  var MIN_PROACTIVE_TAIL_MESSAGE_COUNT = 12;
13235
13665
  var DEFAULT_MIN_COMMIT_CLUSTERS_FOR_TRIGGER = 3;
13236
13666
  var TAIL_SIZE_TRIGGER_MULTIPLIER = 3;
13237
- var FORCE_COMPARTMENT_PERCENTAGE = 80;
13238
13667
  var BLOCK_UNTIL_DONE_PERCENTAGE = 95;
13239
13668
  var CONTENT_TAG_OWNER_SUFFIX = /:(?:p|file)\d+$/;
13240
13669
  function tagOwnerMessageId(row) {
@@ -13271,7 +13700,7 @@ function estimateUntaggedInMemoryTailUpperBound(db, sessionId, inMemoryTail, tag
13271
13700
  function getProactiveCompartmentTriggerPercentage(executeThresholdPercentage) {
13272
13701
  return Math.max(0, executeThresholdPercentage - PROACTIVE_TRIGGER_OFFSET_PERCENTAGE);
13273
13702
  }
13274
- function estimateProjectedPostDropPercentage(db, sessionId, usage, activeTags, clearReasoningAge, clearedReasoningThroughTag) {
13703
+ function estimateProjectedPostDropPercentage(db, sessionId, usage, activeTags, clearReasoningAge, clearedReasoningThroughTag, canClearReasoning) {
13275
13704
  const totalActiveBytes = activeTags.reduce((sum, tag) => sum + tag.byteSize + tag.reasoningByteSize, 0);
13276
13705
  if (totalActiveBytes === 0)
13277
13706
  return null;
@@ -13282,7 +13711,7 @@ function estimateProjectedPostDropPercentage(db, sessionId, usage, activeTags, c
13282
13711
  droppableBytes += activeTags.filter((tag) => pendingDropTagIds.has(tag.tagNumber)).reduce((sum, tag) => sum + tag.byteSize + tag.reasoningByteSize, 0);
13283
13712
  }
13284
13713
  const maxTag = activeTags.reduce((max, t) => Math.max(max, t.tagNumber), 0);
13285
- if (clearReasoningAge !== undefined && clearedReasoningThroughTag !== undefined) {
13714
+ if (canClearReasoning && clearReasoningAge !== undefined && clearedReasoningThroughTag !== undefined) {
13286
13715
  const reasoningAgeCutoff = maxTag - clearReasoningAge;
13287
13716
  for (const tag of activeTags) {
13288
13717
  if (tag.type !== "message")
@@ -13382,7 +13811,7 @@ function getUnsummarizedTailInfo(db, sessionId, triggerBudget, usage, executeThr
13382
13811
  }
13383
13812
  });
13384
13813
  }
13385
- 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) {
13386
13815
  if (sessionMeta.compartmentInProgress) {
13387
13816
  sessionLog(sessionId, `compartment trigger: skipped — historian already in progress (usage=${usage.percentage.toFixed(1)}%)`);
13388
13817
  return { shouldFire: false };
@@ -13436,18 +13865,20 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
13436
13865
  }
13437
13866
  return { shouldFire: false };
13438
13867
  }
13439
- 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);
13440
13870
  const relativePostDropTarget = executeThresholdPercentage * POST_DROP_TARGET_RATIO;
13441
- if (usage.percentage >= FORCE_COMPARTMENT_PERCENTAGE) {
13871
+ const forceMaterializationPercentage = escalationBands(executeThresholdPercentage).forceMaterializationPercentage;
13872
+ if (usage.percentage >= forceMaterializationPercentage) {
13442
13873
  if (projectedPostDropPercentage !== null && projectedPostDropPercentage <= relativePostDropTarget) {
13443
- 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)}%)`);
13444
13875
  return { shouldFire: false };
13445
13876
  }
13446
13877
  sessionLog(sessionId, `compartment trigger: force-firing at ${usage.percentage.toFixed(1)}% (projected post-drop ${projectedPostDropPercentage?.toFixed(1) ?? "none"}%)`);
13447
13878
  if (tailInfo.boundarySnapshot && hasRunnableCompartmentWindow(tailInfo.boundarySnapshot)) {
13448
13879
  return {
13449
13880
  shouldFire: true,
13450
- reason: "force_80",
13881
+ reason: "force_band",
13451
13882
  boundarySnapshot: tailInfo.boundarySnapshot
13452
13883
  };
13453
13884
  }
@@ -13472,9 +13903,9 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
13472
13903
  });
13473
13904
  });
13474
13905
  if (hasRunnableCompartmentWindow(scaledBoundary)) {
13475
- return { shouldFire: true, reason: "force_80", boundarySnapshot: scaledBoundary };
13906
+ return { shouldFire: true, reason: "force_band", boundarySnapshot: scaledBoundary };
13476
13907
  }
13477
- 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");
13478
13909
  return { shouldFire: false };
13479
13910
  }
13480
13911
  const clusterEnabled = commitClusterTrigger?.enabled ?? true;
@@ -13639,11 +14070,6 @@ ${body}${hintText}
13639
14070
  </system-reminder>`;
13640
14071
  }
13641
14072
 
13642
- // ../plugin/src/hooks/magic-context/sentinel.ts
13643
- function modelAcceptsEmptyContent(providerID) {
13644
- return providerID === "anthropic";
13645
- }
13646
-
13647
14073
  // ../plugin/src/hooks/magic-context/supersession-reclaim.ts
13648
14074
  var TODOWRITE_KEEP = 1;
13649
14075
  var CTX_REDUCE_KEEP = 5;
@@ -14958,16 +15384,16 @@ function sealDeliveredAfterUnconfirmedSend(db, sessionId) {
14958
15384
  }
14959
15385
  }
14960
15386
  var channel1StateBySession = new Map;
14961
- function setPiChannel1Baseline(sessionId, state) {
14962
- channel1StateBySession.set(sessionId, state);
15387
+ function setPiChannel1Baseline(sessionId, state2) {
15388
+ channel1StateBySession.set(sessionId, state2);
14963
15389
  }
14964
15390
  function clearPiChannel1State(sessionId) {
14965
15391
  channel1StateBySession.delete(sessionId);
14966
15392
  }
14967
15393
  function markPiChannel1Reduced(sessionId, db) {
14968
- const state = channel1StateBySession.get(sessionId);
14969
- if (state)
14970
- state.reducedSinceRefresh = true;
15394
+ const state2 = channel1StateBySession.get(sessionId);
15395
+ if (state2)
15396
+ state2.reducedSinceRefresh = true;
14971
15397
  if (db)
14972
15398
  resetLastNudgeCycle(db, sessionId);
14973
15399
  }
@@ -15042,11 +15468,11 @@ function computeTailTokenEstimatePi(messages) {
15042
15468
  }
15043
15469
  function maybeChannel1ReminderForToolResult(args) {
15044
15470
  const { db, sessionId, toolName } = args;
15045
- const state = channel1StateBySession.get(sessionId);
15046
- if (!state)
15471
+ const state2 = channel1StateBySession.get(sessionId);
15472
+ if (!state2)
15047
15473
  return null;
15048
15474
  if (toolName === "ctx_reduce") {
15049
- state.reducedSinceRefresh = true;
15475
+ state2.reducedSinceRefresh = true;
15050
15476
  resetLastNudgeCycle(db, sessionId);
15051
15477
  return null;
15052
15478
  }
@@ -15055,21 +15481,21 @@ function maybeChannel1ReminderForToolResult(args) {
15055
15481
  return null;
15056
15482
  if (text.includes(CHANNEL1_SENTINEL))
15057
15483
  return null;
15058
- state.turnToolTokens += toolOutputTokens(text);
15059
- if (state.reducedSinceRefresh)
15484
+ state2.turnToolTokens += toolOutputTokens(text);
15485
+ if (state2.reducedSinceRefresh)
15060
15486
  return null;
15061
- const undroppedTokens = state.tailToolTokens + state.turnToolTokens;
15487
+ const undroppedTokens = state2.tailToolTokens + state2.turnToolTokens;
15062
15488
  const pressure = computePressure({
15063
- lastInputTokens: state.lastInputTokens,
15064
- turnToolTokens: state.turnToolTokens,
15065
- contextLimit: state.contextLimit,
15066
- executeThresholdPercentage: state.executeThresholdPercentage
15489
+ lastInputTokens: state2.lastInputTokens,
15490
+ turnToolTokens: state2.turnToolTokens,
15491
+ contextLimit: state2.contextLimit,
15492
+ executeThresholdPercentage: state2.executeThresholdPercentage
15067
15493
  });
15068
- const workingWindowTokens = Math.round(state.contextLimit * state.executeThresholdPercentage / 100);
15494
+ const workingWindowTokens = Math.round(state2.contextLimit * state2.executeThresholdPercentage / 100);
15069
15495
  const decision = decideChannel1({
15070
15496
  undroppedTokens,
15071
15497
  pressure,
15072
- estimatedInputTokens: state.lastInputTokens + state.turnToolTokens,
15498
+ estimatedInputTokens: state2.lastInputTokens + state2.turnToolTokens,
15073
15499
  workingWindowTokens,
15074
15500
  lastNudgeUndropped: getLastNudgeUndropped(db, sessionId),
15075
15501
  lastNudgeLevel: getLastNudgeLevel(db, sessionId),
@@ -15081,18 +15507,18 @@ function maybeChannel1ReminderForToolResult(args) {
15081
15507
  return null;
15082
15508
  return {
15083
15509
  type: "text",
15084
- text: buildChannel1Reminder(decision.level, decision.undroppedTokens, state.oldestReclaimableToolTags)
15510
+ text: buildChannel1Reminder(decision.level, decision.undroppedTokens, state2.oldestReclaimableToolTags)
15085
15511
  };
15086
15512
  }
15087
15513
  var CHANNEL2_NUDGE_CUSTOM_TYPE = "magic-context:ceiling-nudge";
15088
15514
  function maybeDeliverChannel2Pi(pi, db, sessionId, deliverAs = "followUp") {
15089
- let state;
15515
+ let state2;
15090
15516
  try {
15091
- state = getChannel2NudgeState(db, sessionId);
15517
+ state2 = getChannel2NudgeState(db, sessionId);
15092
15518
  } catch {
15093
15519
  return false;
15094
15520
  }
15095
- if (state !== "pending")
15521
+ if (state2 !== "pending")
15096
15522
  return false;
15097
15523
  const baseline = channel1StateBySession.get(sessionId);
15098
15524
  if (!baseline)
@@ -15764,14 +16190,17 @@ var EMPTY_PI_PROJECT_DOCS = {
15764
16190
  renderedBlock: "",
15765
16191
  canonicalHash: ""
15766
16192
  };
15767
- function readProjectDocsForPiM0(state) {
15768
- 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;
15769
16198
  }
15770
- function memoryProjectPath(state) {
15771
- return state.memoryEnabled === false ? undefined : state.projectIdentity;
16199
+ function memoryProjectPath(state2) {
16200
+ return state2.memoryEnabled === false ? undefined : state2.projectIdentity;
15772
16201
  }
15773
- function resolveWorkspaceRenderContextPi(state, db) {
15774
- const memPath = memoryProjectPath(state);
16202
+ function resolveWorkspaceRenderContextPi(state2, db) {
16203
+ const memPath = memoryProjectPath(state2);
15775
16204
  if (!memPath) {
15776
16205
  return {
15777
16206
  identities: [],
@@ -15847,12 +16276,12 @@ function piImageFromDataUrl(dataUrl) {
15847
16276
  return null;
15848
16277
  return { type: "image", mimeType: match[1], data: match[2] };
15849
16278
  }
15850
- function resolveMuralForM0Pi(state, db, modelKey, budgetTokens) {
15851
- if (state.mural)
15852
- return state.mural;
15853
- if (!state.muralEnabled)
16279
+ function resolveMuralForM0Pi(state2, db, modelKey, budgetTokens) {
16280
+ if (state2.mural)
16281
+ return state2.mural;
16282
+ if (!state2.muralEnabled)
15854
16283
  return;
15855
- return resolveMuralWire(db, memoryProjectPath(state), modelKey, true, budgetTokens);
16284
+ return resolveMuralWire(db, memoryProjectPath(state2), modelKey, true, budgetTokens);
15856
16285
  }
15857
16286
  function cachedInjectionTokenCounts(sessionId, m0, m1) {
15858
16287
  const cached = injectionTokenCountsBySession.get(sessionId);
@@ -15905,17 +16334,17 @@ function trimPiMessagesToCachedBoundary(db, sessionId, piMessages, entryIds) {
15905
16334
  function setCachedBoundary(db, sessionId, boundary) {
15906
16335
  db.prepare("UPDATE session_meta SET cached_m0_last_baseline_end_message_id = ? WHERE session_id = ?").run(boundary, sessionId);
15907
16336
  }
15908
- function getCachedMarkers(db, state, compartmentsForNormalization) {
15909
- const meta = getOrCreateSessionMeta(db, state.sessionId);
16337
+ function getCachedMarkers(db, state2, compartmentsForNormalization) {
16338
+ const meta = getOrCreateSessionMeta(db, state2.sessionId);
15910
16339
  if (!meta.cachedM0Bytes)
15911
16340
  return null;
15912
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) {
15913
16342
  return null;
15914
16343
  }
15915
- const compartments = compartmentsForNormalization ?? getCompartments(db, state.sessionId);
16344
+ const compartments = compartmentsForNormalization ?? getRenderableCompartmentsPi(db, state2);
15916
16345
  const cachedUpgradeIdentity = decodeCachedM0UpgradeIdentity(meta.cachedM0UpgradeState);
15917
16346
  const maxCompartmentSeq = normalizeCachedMaxCompartmentSeq(meta.cachedM0MaxCompartmentSeq, compartments);
15918
- const cachedBoundary = getCachedBoundary(db, state.sessionId);
16347
+ const cachedBoundary = getCachedBoundary(db, state2.sessionId);
15919
16348
  const liveBoundary = lastBaselineEndMessageId(compartments);
15920
16349
  if (maxCompartmentSeq >= 0 && cachedBoundary === null && liveBoundary !== null) {
15921
16350
  return null;
@@ -15943,38 +16372,38 @@ function lastBaselineEndMessageId(compartments) {
15943
16372
  const last = compartments.at(-1);
15944
16373
  return last?.endMessageId && last.endMessageId.length > 0 ? last.endMessageId : null;
15945
16374
  }
15946
- function readCurrentMarkers(db, state, projectDocsHash) {
15947
- return readCurrentMarkersFromCompartments(db, state, getCompartments(db, state.sessionId), projectDocsHash);
16375
+ function readCurrentMarkers(db, state2, projectDocsHash) {
16376
+ return readCurrentMarkersFromCompartments(db, state2, getRenderableCompartmentsPi(db, state2), projectDocsHash);
15948
16377
  }
15949
- function readCurrentMarkersFromCompartments(db, state, compartments, projectDocsHash) {
15950
- const memPath = memoryProjectPath(state);
15951
- const workspace = resolveWorkspaceRenderContextPi(state, db);
16378
+ function readCurrentMarkersFromCompartments(db, state2, compartments, projectDocsHash) {
16379
+ const memPath = memoryProjectPath(state2);
16380
+ const workspace = resolveWorkspaceRenderContextPi(state2, db);
15952
16381
  const maxMemoryId = memPath ? workspace.isWorkspaced ? getMaxMemoryIdForProjects(db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryIdForProjects(db, [memPath]) : 0;
15953
16382
  const projectState = memPath ? getProjectState(db, memPath) : undefined;
15954
16383
  const globalState = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH);
15955
16384
  return {
15956
16385
  maxCompartmentSeq: compartments.length > 0 ? compartments.reduce((max, compartment) => compartment.sequence > max ? compartment.sequence : max, EMPTY_MAX_COMPARTMENT_SEQ) : EMPTY_MAX_COMPARTMENT_SEQ,
15957
16386
  maxMemoryId,
15958
- maxMutationId: getMaxM0MutationId(db, state.sessionId) ?? 0,
16387
+ maxMutationId: getMaxM0MutationId(db, state2.sessionId) ?? 0,
15959
16388
  maxMemoryMutationId: memPath ? workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(db, workspace.expandedIdentities) ?? 0 : getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
15960
16389
  projectMemoryEpoch: projectState?.projectMemoryEpoch ?? 0,
15961
16390
  workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(db, workspace.identities) : null,
15962
16391
  projectUserProfileVersion: globalState?.projectUserProfileVersion ?? 0,
15963
- projectDocsHash: projectDocsHash ?? readProjectDocsForPiM0(state).canonicalHash,
15964
- sessionFactsVersion: getSessionFactsVersion(db, state.sessionId),
16392
+ projectDocsHash: projectDocsHash ?? readProjectDocsForPiM0(state2).canonicalHash,
16393
+ sessionFactsVersion: getSessionFactsVersion(db, state2.sessionId),
15965
16394
  materializedAt: Date.now(),
15966
16395
  upgradeState: `${PI_M0_UPGRADE_STATE}:${compartments.some((c) => c.legacy === 1) ? "legacy" : "ready"}`,
15967
16396
  compartmentRenderEpoch: COMPARTMENT_RENDER_EPOCH,
15968
16397
  lastBaselineEndMessageId: lastBaselineEndMessageId(compartments),
15969
- systemHash: (state.hardSignals ?? EMPTY_PI_HARD_SIGNALS).systemHash,
15970
- modelKey: (state.hardSignals ?? EMPTY_PI_HARD_SIGNALS).modelKey,
15971
- 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
15972
16401
  };
15973
16402
  }
15974
- function mustMaterializePi(state, db, currentCompartmentsOverride) {
15975
- const meta = getOrCreateSessionMeta(db, state.sessionId);
15976
- const currentCompartments = currentCompartmentsOverride ?? getCompartments(db, state.sessionId);
15977
- 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);
15978
16407
  if (!meta.cachedM0Bytes)
15979
16408
  return { value: true, reason: "first_render" };
15980
16409
  if (!meta.cachedM1Bytes)
@@ -15982,21 +16411,21 @@ function mustMaterializePi(state, db, currentCompartmentsOverride) {
15982
16411
  if (!decodeCachedM0(meta.cachedM0Bytes)) {
15983
16412
  return { value: true, reason: "cache_invalid" };
15984
16413
  }
15985
- const cached = getCachedMarkers(db, state, currentCompartments);
16414
+ const cached = getCachedMarkers(db, state2, currentCompartments);
15986
16415
  if (cached === null) {
15987
16416
  return { value: true, reason: "cache_invalid" };
15988
16417
  }
15989
16418
  if (cached.compartmentRenderEpoch !== current.compartmentRenderEpoch) {
15990
16419
  return { value: true, reason: "compartment_render_epoch" };
15991
16420
  }
15992
- const hard = state.hardSignals ?? EMPTY_PI_HARD_SIGNALS;
16421
+ const hard = state2.hardSignals ?? EMPTY_PI_HARD_SIGNALS;
15993
16422
  if (hard.modelKey !== "" && hard.modelKey !== (meta.cachedM0ModelKey ?? "")) {
15994
16423
  return { value: true, reason: "model_change" };
15995
16424
  }
15996
16425
  if (hard.systemHash !== "" && hard.systemHash !== (meta.cachedM0SystemHash ?? "")) {
15997
16426
  return { value: true, reason: "system_hash" };
15998
16427
  }
15999
- if (meta.cachedM0ProjectIdentity !== null && meta.cachedM0ProjectIdentity !== state.projectIdentity) {
16428
+ if (meta.cachedM0ProjectIdentity !== null && meta.cachedM0ProjectIdentity !== state2.projectIdentity) {
16000
16429
  return { value: true, reason: "project_change" };
16001
16430
  }
16002
16431
  if (hard.cacheExpired && hard.lastResponseTime > 0 && hard.lastResponseTime > (meta.cachedM0MaterializedAt ?? 0)) {
@@ -16026,9 +16455,9 @@ ${memories.map((memory) => `- ${escapeXmlContent(memory.content)}`).join(`
16026
16455
  `)}
16027
16456
  </${wrapper}>`;
16028
16457
  }
16029
- function renderM0Pi(state, db, projectDocs = readProjectDocsForPiM0(state).renderedBlock, decayPressureMultiplier = 1, memoriesOverride, compartmentsOverride, userProfileOverride, workspaceOverride, mural) {
16030
- const memPath = memoryProjectPath(state);
16031
- 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);
16032
16461
  const allMemories = memoriesOverride ?? (memPath ? workspace.isWorkspaced ? getMemoriesByProjects(db, workspace.expandedIdentities, ["active", "permanent"], Date.now(), workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(db, memPath, ["active", "permanent"]) : []);
16033
16462
  const memoryRenderOptions = {
16034
16463
  sourceNameByMemoryId: sourceNamesForPiMemories({
@@ -16037,23 +16466,25 @@ function renderM0Pi(state, db, projectDocs = readProjectDocsForPiM0(state).rende
16037
16466
  workspace
16038
16467
  })
16039
16468
  };
16040
- 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;
16041
16470
  const memoryBlock = memories.length > 0 ? renderMemoryBlockV2(memories, "project-memory", memoryRenderOptions) : undefined;
16042
- const baseHistoryBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
16471
+ const baseHistoryBudget = state2.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
16043
16472
  const decayed = renderDecayedCompartments({
16044
- compartments: compartmentsOverride ?? getCompartments(db, state.sessionId),
16473
+ compartments: compartmentsOverride ?? getRenderableCompartmentsPi(db, state2),
16045
16474
  historyBudgetTokens: baseHistoryBudget / Math.max(1, decayPressureMultiplier)
16046
16475
  });
16047
16476
  const sections = [];
16048
16477
  if (projectDocs.length > 0)
16049
16478
  sections.push(projectDocs);
16050
- 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);
16051
16480
  const userProfile = renderUserProfileBlock(db, "user-profile", trimmedProfile);
16052
16481
  if (userProfile.length > 0)
16053
16482
  sections.push(userProfile);
16054
- sections.push(decayed.length > 0 ? `<session-history>
16483
+ if (!state2.compactionOff) {
16484
+ sections.push(decayed.length > 0 ? `<session-history>
16055
16485
  ${decayed}
16056
16486
  </session-history>` : "<session-history></session-history>");
16487
+ }
16057
16488
  if (memoryBlock)
16058
16489
  sections.push(memoryBlock);
16059
16490
  if (mural?.enabled && mural.supportsVision && mural.dataUrl) {
@@ -16065,18 +16496,18 @@ The project memory mural image follows.
16065
16496
 
16066
16497
  `).trim();
16067
16498
  }
16068
- function renderedMemoryIdsForPi(state, memories, workspace, db) {
16499
+ function renderedMemoryIdsForPi(state2, memories, workspace, db) {
16069
16500
  if (memories.length === 0)
16070
16501
  return [];
16071
- const resolvedWorkspace = workspace ?? (db ? resolveWorkspaceRenderContextPi(state, db) : undefined);
16502
+ const resolvedWorkspace = workspace ?? (db ? resolveWorkspaceRenderContextPi(state2, db) : undefined);
16072
16503
  const renderOptions = resolvedWorkspace ? {
16073
16504
  sourceNameByMemoryId: sourceNamesForPiMemories({
16074
16505
  memories,
16075
- projectPath: memoryProjectPath(state),
16506
+ projectPath: memoryProjectPath(state2),
16076
16507
  workspace: resolvedWorkspace
16077
16508
  })
16078
16509
  } : {};
16079
- 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);
16080
16511
  return trimmed.renderOrder.map((memory) => memory.id);
16081
16512
  }
16082
16513
  function isTransientSqliteLockError(error) {
@@ -16099,11 +16530,11 @@ class PiMaterializeContentionError extends Error {
16099
16530
  this.name = "PiMaterializeContentionError";
16100
16531
  }
16101
16532
  }
16102
- function readFrozenM0InputsPi(state, db, docs = readProjectDocsForPiM0(state), memoryCutoff) {
16103
- const memPath = memoryProjectPath(state);
16533
+ function readFrozenM0InputsPi(state2, db, docs = readProjectDocsForPiM0(state2), memoryCutoff) {
16534
+ const memPath = memoryProjectPath(state2);
16104
16535
  const read = db.transaction(() => {
16105
- const workspace = resolveWorkspaceRenderContextPi(state, db);
16106
- const compartments = getCompartments(db, state.sessionId);
16536
+ const workspace = resolveWorkspaceRenderContextPi(state2, db);
16537
+ const compartments = getRenderableCompartmentsPi(db, state2);
16107
16538
  const memories = memPath ? workspace.isWorkspaced ? getMemoriesByProjects(db, workspace.expandedIdentities, ["active", "permanent"], memoryCutoff, workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(db, memPath, ["active", "permanent"], memoryCutoff) : [];
16108
16539
  const userProfile = safeGetActiveUserMemoriesPi(db);
16109
16540
  const projectState = memPath ? getProjectState(db, memPath) : undefined;
@@ -16111,72 +16542,72 @@ function readFrozenM0InputsPi(state, db, docs = readProjectDocsForPiM0(state), m
16111
16542
  const markers = {
16112
16543
  maxCompartmentSeq: compartments.reduce((max, compartment) => compartment.sequence > max ? compartment.sequence : max, EMPTY_MAX_COMPARTMENT_SEQ),
16113
16544
  maxMemoryId: memPath ? workspace.isWorkspaced ? getMaxMemoryIdForProjects(db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryIdForProjects(db, [memPath]) : 0,
16114
- maxMutationId: getMaxM0MutationId(db, state.sessionId) ?? 0,
16545
+ maxMutationId: getMaxM0MutationId(db, state2.sessionId) ?? 0,
16115
16546
  maxMemoryMutationId: memPath ? workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(db, workspace.expandedIdentities) ?? 0 : getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
16116
16547
  projectMemoryEpoch: projectState?.projectMemoryEpoch ?? 0,
16117
16548
  workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(db, workspace.identities) : null,
16118
16549
  projectUserProfileVersion: globalState?.projectUserProfileVersion ?? 0,
16119
16550
  projectDocsHash: docs.canonicalHash,
16120
- sessionFactsVersion: getSessionFactsVersion(db, state.sessionId),
16551
+ sessionFactsVersion: getSessionFactsVersion(db, state2.sessionId),
16121
16552
  materializedAt: memoryCutoff ?? Date.now(),
16122
16553
  upgradeState: `${PI_M0_UPGRADE_STATE}:${compartments.some((c) => c.legacy === 1) ? "legacy" : "ready"}`,
16123
16554
  compartmentRenderEpoch: COMPARTMENT_RENDER_EPOCH,
16124
16555
  lastBaselineEndMessageId: lastBaselineEndMessageId(compartments),
16125
- systemHash: (state.hardSignals ?? EMPTY_PI_HARD_SIGNALS).systemHash,
16126
- modelKey: (state.hardSignals ?? EMPTY_PI_HARD_SIGNALS).modelKey,
16127
- 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
16128
16559
  };
16129
16560
  return { docs, markers, compartments, memories, userProfile, workspace };
16130
16561
  });
16131
16562
  return read();
16132
16563
  }
16133
- function renderFreshM0PiNonPersisted(state, db) {
16134
- const docs = readProjectDocsForPiM0(state);
16135
- const cachedMaterializedAt = getOrCreateSessionMeta(db, state.sessionId).cachedM0MaterializedAt ?? 0;
16136
- 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);
16137
16568
  frozen.markers.materializedAt = cachedMaterializedAt;
16138
- const historyBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
16139
- const memoryBudget = state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
16140
- const mural = resolveMuralForM0Pi(state, db, frozen.markers.modelKey, memoryBudget);
16141
- 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);
16142
16573
  let dpm = 1;
16143
- 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);
16144
16575
  let attempts = 0;
16145
16576
  while (historyBudget > 0 && historySliceTokensPi(m0) > historyBudget * 1.05 && attempts < 3) {
16146
16577
  dpm *= 1.15;
16147
- 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);
16148
16579
  attempts += 1;
16149
16580
  }
16150
16581
  return {
16151
16582
  m0,
16152
16583
  snapshotMarkers: frozen.markers,
16153
- renderedMemoryIds: renderedMemoryIdsForPi(state, frozen.memories, frozen.workspace, db)
16584
+ renderedMemoryIds: renderedMemoryIdsForPi(state2, frozen.memories, frozen.workspace, db)
16154
16585
  };
16155
16586
  }
16156
- function materializeM0Pi(state, db) {
16157
- const docs = readProjectDocsForPiM0(state);
16587
+ function materializeM0Pi(state2, db) {
16588
+ const docs = readProjectDocsForPiM0(state2);
16158
16589
  const foldMaterializedAt = Date.now();
16159
- const frozen = readFrozenM0InputsPi(state, db, docs, foldMaterializedAt);
16590
+ const frozen = readFrozenM0InputsPi(state2, db, docs, foldMaterializedAt);
16160
16591
  const snapshotMarkers = frozen.markers;
16161
16592
  const snapshotMemories = frozen.memories;
16162
16593
  const snapshotCompartments = frozen.compartments;
16163
16594
  const snapshotUserProfile = frozen.userProfile;
16164
- const renderedMemoryIds = renderedMemoryIdsForPi(state, snapshotMemories, frozen.workspace, db);
16165
- const memoryBudget = state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
16166
- 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);
16167
16598
  const frozenMuralDataUrl = mural?.enabled && mural.supportsVision ? mural.dataUrl ?? null : null;
16168
16599
  const frozenMuralHash = mural?.enabled && mural.supportsVision ? mural.contentHash ?? null : null;
16169
16600
  let decayPressureMultiplier = 1;
16170
- let m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile, frozen.workspace, mural);
16171
- 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;
16172
16603
  let attempts = 0;
16173
16604
  while (historyBudget > 0 && historySliceTokensPi(m0) > historyBudget * 1.05 && attempts < 3) {
16174
16605
  decayPressureMultiplier *= 1.15;
16175
- 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);
16176
16607
  attempts += 1;
16177
16608
  }
16178
16609
  const m0Bytes = Buffer.from(m0, "utf8");
16179
- const phase3ProjectDocsHash = readProjectDocsForPiM0(state).canonicalHash;
16610
+ const phase3ProjectDocsHash = readProjectDocsForPiM0(state2).canonicalHash;
16180
16611
  try {
16181
16612
  db.exec("BEGIN IMMEDIATE");
16182
16613
  } catch (error) {
@@ -16186,7 +16617,7 @@ function materializeM0Pi(state, db) {
16186
16617
  throw error;
16187
16618
  }
16188
16619
  try {
16189
- const current = readCurrentMarkers(db, state, phase3ProjectDocsHash);
16620
+ const current = readCurrentMarkers(db, state2, phase3ProjectDocsHash);
16190
16621
  const memoryEpochStale = current.workspaceFingerprint !== null || snapshotMarkers.workspaceFingerprint !== null ? current.workspaceFingerprint !== snapshotMarkers.workspaceFingerprint : current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch;
16191
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;
16192
16623
  if (stale) {
@@ -16194,9 +16625,9 @@ function materializeM0Pi(state, db) {
16194
16625
  throw new PiMaterializeContentionError("snapshot changed before persist");
16195
16626
  }
16196
16627
  snapshotMarkers.materializedAt = foldMaterializedAt;
16197
- const m1Render = renderM1PiWithMetadata(state, db, snapshotMarkers, renderedMemoryIds);
16628
+ const m1Render = renderM1PiWithMetadata(state2, db, snapshotMarkers, renderedMemoryIds);
16198
16629
  const m1Bytes = Buffer.from(m1Render.text, "utf8");
16199
- persistCachedM0(db, state.sessionId, {
16630
+ persistCachedM0(db, state2.sessionId, {
16200
16631
  m0Bytes,
16201
16632
  muralDataUrl: frozenMuralDataUrl,
16202
16633
  muralHash: frozenMuralHash,
@@ -16216,10 +16647,10 @@ function materializeM0Pi(state, db) {
16216
16647
  modelKey: snapshotMarkers.modelKey,
16217
16648
  projectIdentity: snapshotMarkers.projectIdentity
16218
16649
  });
16219
- db.prepare("UPDATE session_meta SET memory_block_count = ?, memory_block_ids = ? WHERE session_id = ?").run(renderedMemoryIds.length, JSON.stringify(renderedMemoryIds), state.sessionId);
16220
- 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);
16221
16652
  db.exec("COMMIT");
16222
- rememberPiMuralPayload(state.sessionId, frozenMuralDataUrl, frozenMuralHash);
16653
+ rememberPiMuralPayload(state2.sessionId, frozenMuralDataUrl, frozenMuralHash);
16223
16654
  return {
16224
16655
  m0,
16225
16656
  m1: m1Render.text,
@@ -16233,11 +16664,11 @@ function materializeM0Pi(state, db) {
16233
16664
  throw error;
16234
16665
  }
16235
16666
  }
16236
- function materializeM0PiWithRetry(state, db, maxRetries = 3) {
16667
+ function materializeM0PiWithRetry(state2, db, maxRetries = 3) {
16237
16668
  let lastError = null;
16238
16669
  for (let attempt = 0;attempt < maxRetries; attempt++) {
16239
16670
  try {
16240
- return materializeM0Pi(state, db);
16671
+ return materializeM0Pi(state2, db);
16241
16672
  } catch (error) {
16242
16673
  if (!(error instanceof PiMaterializeContentionError))
16243
16674
  throw error;
@@ -16279,10 +16710,10 @@ ${lines.join(`
16279
16710
  count: mutations.length
16280
16711
  };
16281
16712
  }
16282
- function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, compartmentsOverride) {
16713
+ function renderM1PiWithMetadata(state2, db, markers, renderedMemoryIds, compartmentsOverride) {
16283
16714
  const sections = [];
16284
- const workspace = resolveWorkspaceRenderContextPi(state, db);
16285
- const memPath = memoryProjectPath(state);
16715
+ const workspace = resolveWorkspaceRenderContextPi(state2, db);
16716
+ const memPath = memoryProjectPath(state2);
16286
16717
  const memoryUpdates = memPath ? renderMemoryUpdatesBlockPi({
16287
16718
  db,
16288
16719
  projectPath: memPath,
@@ -16292,7 +16723,7 @@ function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, compartme
16292
16723
  }) : { block: undefined, count: 0 };
16293
16724
  if (memoryUpdates.block)
16294
16725
  sections.push(memoryUpdates.block);
16295
- 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);
16296
16727
  if (newCompartments.length > 0) {
16297
16728
  const body = newCompartments.map((compartment) => renderCompartmentAtTier(compartment, 1)).join(`
16298
16729
 
@@ -16303,7 +16734,7 @@ ${body}
16303
16734
  }
16304
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) : [];
16305
16736
  if (newMemories.length > 0) {
16306
- const memoryBudget = state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
16737
+ const memoryBudget = state2.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
16307
16738
  const memoryRenderOptions = {
16308
16739
  sourceNameByMemoryId: sourceNamesForPiMemories({
16309
16740
  memories: newMemories,
@@ -16311,14 +16742,14 @@ ${body}
16311
16742
  workspace
16312
16743
  })
16313
16744
  };
16314
- 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;
16315
16746
  const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories", memoryRenderOptions);
16316
16747
  if (newMemoriesBlock)
16317
16748
  sections.push(newMemoriesBlock);
16318
16749
  }
16319
16750
  const currentUserProfileVersion = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH)?.projectUserProfileVersion ?? 0;
16320
16751
  if (currentUserProfileVersion !== markers.projectUserProfileVersion) {
16321
- const profileBudget = state.userProfileBudgetTokens ?? DEFAULT_USER_PROFILE_BUDGET_TOKENS;
16752
+ const profileBudget = state2.userProfileBudgetTokens ?? DEFAULT_USER_PROFILE_BUDGET_TOKENS;
16322
16753
  const trimmedProfile = trimUserMemoriesToBudget(safeGetActiveUserMemoriesPi(db), Math.max(1, Math.floor(profileBudget * 0.25)));
16323
16754
  const profileBlock = renderUserProfileBlock(db, "new-user-profile", trimmedProfile);
16324
16755
  if (profileBlock)
@@ -16331,7 +16762,10 @@ ${body}
16331
16762
  };
16332
16763
  }
16333
16764
  return {
16334
- text: `<session-history-since>
16765
+ text: state2.compactionOff ? `<knowledge-updates>
16766
+ ${sections.join(`
16767
+ `)}
16768
+ </knowledge-updates>` : `<session-history-since>
16335
16769
  ${sections.join(`
16336
16770
  `)}
16337
16771
  </session-history-since>`,
@@ -16427,8 +16861,8 @@ function cachedPiRowMatchesSnapshot(args) {
16427
16861
  return false;
16428
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);
16429
16863
  }
16430
- function adoptCachedPiProjectIdentity(db, state) {
16431
- 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);
16432
16866
  }
16433
16867
  function decodeCachedM1(row, sessionId) {
16434
16868
  if (!row.cached_m1_bytes) {
@@ -16449,12 +16883,12 @@ function applyCachedPiRow(args) {
16449
16883
  markers
16450
16884
  };
16451
16885
  }
16452
- function replayCachedM1Pi(db, state, compartmentsForNormalization) {
16453
- const row = readCachedPiM0M1Row(db, state.sessionId);
16886
+ function replayCachedM1Pi(db, state2, compartmentsForNormalization) {
16887
+ const row = readCachedPiM0M1Row(db, state2.sessionId);
16454
16888
  if (!row) {
16455
- 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}`);
16456
16890
  }
16457
- return applyCachedPiRow({ row, state, compartmentsForNormalization });
16891
+ return applyCachedPiRow({ row, state: state2, compartmentsForNormalization });
16458
16892
  }
16459
16893
  function softRefreshCachedM1Pi(args) {
16460
16894
  args.db.exec("BEGIN IMMEDIATE");
@@ -16471,7 +16905,7 @@ function softRefreshCachedM1Pi(args) {
16471
16905
  if (!sibling) {
16472
16906
  throw new PiMaterializeContentionError(`missing sibling cached m[0]/m[1] for ${args.state.sessionId}`);
16473
16907
  }
16474
- const siblingCompartments = getCompartments(args.db, args.state.sessionId);
16908
+ const siblingCompartments = getRenderableCompartmentsPi(args.db, args.state);
16475
16909
  return {
16476
16910
  ...applyCachedPiRow({
16477
16911
  row: sibling,
@@ -16538,9 +16972,9 @@ function prependM0M1Messages(piMessages, m0, m1, mural) {
16538
16972
  timestamp: baseTimestamp - 1
16539
16973
  });
16540
16974
  }
16541
- function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = false) {
16542
- const currentCompartments = getCompartments(db, state.sessionId);
16543
- 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);
16544
16978
  let m0 = "";
16545
16979
  let m1 = PI_M1_PLACEHOLDER;
16546
16980
  let markers = null;
@@ -16551,7 +16985,7 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16551
16985
  let freshFallbackRenderedMemoryIds = null;
16552
16986
  if (decision.value) {
16553
16987
  try {
16554
- const result = materializeM0PiWithRetry(state, db);
16988
+ const result = materializeM0PiWithRetry(state2, db);
16555
16989
  m0 = result.m0;
16556
16990
  m1 = result.m1;
16557
16991
  markers = result.snapshotMarkers;
@@ -16561,30 +16995,30 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16561
16995
  if (!(error instanceof PiMaterializeContentionError))
16562
16996
  throw error;
16563
16997
  try {
16564
- const cached = replayCachedM1Pi(db, state, currentCompartments);
16998
+ const cached = replayCachedM1Pi(db, state2, currentCompartments);
16565
16999
  contentionExhausted = true;
16566
17000
  m0 = cached.m0;
16567
17001
  m1 = cached.m1;
16568
17002
  markers = cached.markers;
16569
- 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]");
16570
17004
  } catch {
16571
- const fresh = renderFreshM0PiNonPersisted(state, db);
17005
+ const fresh = renderFreshM0PiNonPersisted(state2, db);
16572
17006
  m0 = fresh.m0;
16573
17007
  markers = fresh.snapshotMarkers;
16574
17008
  freshFallbackRenderedMemoryIds = fresh.renderedMemoryIds;
16575
17009
  contentionExhausted = true;
16576
- 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]");
16577
17011
  }
16578
17012
  }
16579
17013
  } else {
16580
- const meta = getOrCreateSessionMeta(db, state.sessionId);
17014
+ const meta = getOrCreateSessionMeta(db, state2.sessionId);
16581
17015
  m0 = decodeCachedM0(meta.cachedM0Bytes) ?? "";
16582
- rememberPiMuralPayload(state.sessionId, meta.cachedM0MuralDataUrl, meta.cachedM0MuralHash);
16583
- markers = getCachedMarkers(db, state, currentCompartments);
17016
+ rememberPiMuralPayload(state2.sessionId, meta.cachedM0MuralDataUrl, meta.cachedM0MuralHash);
17017
+ markers = getCachedMarkers(db, state2, currentCompartments);
16584
17018
  if (!m0 || !markers) {
16585
17019
  decision = { value: true, reason: "cache_invalid" };
16586
17020
  try {
16587
- const result = materializeM0PiWithRetry(state, db);
17021
+ const result = materializeM0PiWithRetry(state2, db);
16588
17022
  m0 = result.m0;
16589
17023
  m1 = result.m1;
16590
17024
  markers = result.snapshotMarkers;
@@ -16593,30 +17027,30 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16593
17027
  } catch (error) {
16594
17028
  if (!(error instanceof PiMaterializeContentionError))
16595
17029
  throw error;
16596
- const fresh = renderFreshM0PiNonPersisted(state, db);
17030
+ const fresh = renderFreshM0PiNonPersisted(state2, db);
16597
17031
  m0 = fresh.m0;
16598
17032
  markers = fresh.snapshotMarkers;
16599
17033
  freshFallbackRenderedMemoryIds = fresh.renderedMemoryIds;
16600
17034
  contentionExhausted = true;
16601
- 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]");
16602
17036
  }
16603
17037
  }
16604
17038
  }
16605
17039
  if (!markers) {
16606
- throw new PiMaterializeContentionError(`missing m[0] markers for ${state.sessionId}`);
17040
+ throw new PiMaterializeContentionError(`missing m[0] markers for ${state2.sessionId}`);
16607
17041
  }
16608
17042
  if (!materialized && markers.projectIdentity === null) {
16609
- adoptCachedPiProjectIdentity(db, state);
16610
- markers = { ...markers, projectIdentity: state.projectIdentity };
17043
+ adoptCachedPiProjectIdentity(db, state2);
17044
+ markers = { ...markers, projectIdentity: state2.projectIdentity };
16611
17045
  }
16612
17046
  if (materialized) {} else if (contentionExhausted && freshFallbackRenderedMemoryIds) {
16613
- const freshM1 = renderM1PiWithMetadata(state, db, markers, freshFallbackRenderedMemoryIds);
17047
+ const freshM1 = renderM1PiWithMetadata(state2, db, markers, freshFallbackRenderedMemoryIds);
16614
17048
  m1 = freshM1.text;
16615
17049
  memoryUpdateCount = freshM1.memoryUpdateCount;
16616
17050
  m1Recomputed = true;
16617
17051
  } else if (contentionExhausted) {} else if (recomputeM1ThisPass) {
16618
17052
  const refreshed = softRefreshCachedM1Pi({
16619
- state,
17053
+ state: state2,
16620
17054
  db,
16621
17055
  m0Bytes: Buffer.from(m0, "utf8"),
16622
17056
  markers,
@@ -16628,7 +17062,7 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16628
17062
  memoryUpdateCount = refreshed.memoryUpdateCount;
16629
17063
  m1Recomputed = refreshed.recomputed;
16630
17064
  } else {
16631
- const replayed = replayCachedM1Pi(db, state, currentCompartments);
17065
+ const replayed = replayCachedM1Pi(db, state2, currentCompartments);
16632
17066
  m0 = replayed.m0;
16633
17067
  m1 = replayed.m1;
16634
17068
  markers = replayed.markers;
@@ -16636,14 +17070,14 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16636
17070
  const M0_DRIFT_RATIO_FLOOR_TOKENS = 500;
16637
17071
  const M1_DRIFT_RATIO = 0.15;
16638
17072
  const M1_ABSOLUTE_CAP_RATIO = 0.2;
16639
- 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;
16640
17074
  const m1HasContent = m1 !== PI_M1_PLACEHOLDER;
16641
- const { m0Tokens, m1Tokens } = cachedInjectionTokenCounts(state.sessionId, m0, m1);
17075
+ const { m0Tokens, m1Tokens } = cachedInjectionTokenCounts(state2.sessionId, m0, m1);
16642
17076
  const m1OverAbsoluteCap = m1HasContent && m1Tokens > m1AbsoluteBudget;
16643
17077
  if (!materialized && !contentionExhausted && m1Recomputed && recomputeM1ThisPass && (memoryUpdateCount > 40 || m1OverAbsoluteCap || m1HasContent && m0Tokens >= M0_DRIFT_RATIO_FLOOR_TOKENS && m1Tokens > m0Tokens * M1_DRIFT_RATIO)) {
16644
17078
  decision = { value: true, reason: "drift" };
16645
17079
  try {
16646
- const result = materializeM0PiWithRetry(state, db);
17080
+ const result = materializeM0PiWithRetry(state2, db);
16647
17081
  m0 = result.m0;
16648
17082
  m1 = result.m1;
16649
17083
  markers = result.snapshotMarkers;
@@ -16666,17 +17100,17 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16666
17100
  }
16667
17101
  }
16668
17102
  const skippedVisibleMessages = boundaryId ? trimPiMessagesToBoundary(piMessages, entryIds, boundaryId) : 0;
16669
- const muralWire = m0.includes("<memory-mural>") ? muralForWire(state.sessionId) : undefined;
17103
+ const muralWire = m0.includes("<memory-mural>") ? muralForWire(state2.sessionId) : undefined;
16670
17104
  if (!muralWire)
16671
17105
  m0 = stripMemoryMuralBlock(m0);
16672
17106
  prependM0M1Messages(piMessages, m0, m1, muralWire);
16673
- sessionLog(state.sessionId, `injected m[0]/m[1] into Pi messages (${m0.length} + ${m1.length} bytes, materialized=${materialized}${decision.reason ? ` reason=${decision.reason}` : ""})`);
16674
- const memPath = memoryProjectPath(state);
16675
- 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);
16676
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;
16677
17111
  return {
16678
17112
  injected: true,
16679
- compartmentCount: getCompartments(db, state.sessionId).length,
17113
+ compartmentCount: currentCompartments.length,
16680
17114
  factCount: 0,
16681
17115
  memoryCount,
16682
17116
  skippedVisibleMessages,
@@ -16738,6 +17172,16 @@ function isSentinelPart(part) {
16738
17172
  return p.type === "text" && typeof p.text === "string" && (p.text === "" || p.text === WHOLE_MESSAGE_PLACEHOLDER_TEXT);
16739
17173
  }
16740
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
+
16741
17185
  // src/pi-historian-runner.ts
16742
17186
  import * as crypto2 from "node:crypto";
16743
17187
 
@@ -19928,6 +20372,7 @@ async function runPiHistorian(deps) {
19928
20372
  twoPass,
19929
20373
  thinkingLevel,
19930
20374
  memoryEnabled,
20375
+ allowHomeProject,
19931
20376
  autoPromote,
19932
20377
  userMemoriesEnabled,
19933
20378
  onPublished,
@@ -20066,7 +20511,7 @@ async function runPiHistorian(deps) {
20066
20511
  rollbackDrainReservation();
20067
20512
  return;
20068
20513
  }
20069
- const projectPath = resolveProjectIdentityForSession(directory);
20514
+ const projectPath = resolveProjectIdentityForSession(directory, allowHomeProject);
20070
20515
  if (!projectPath) {
20071
20516
  rollbackDrainReservation();
20072
20517
  return;
@@ -22021,7 +22466,6 @@ function extractStableId(msg, index, entryIds) {
22021
22466
  }
22022
22467
 
22023
22468
  // src/context-handler.ts
22024
- var FORCE_MATERIALIZATION_PERCENTAGE = 85;
22025
22469
  var EMERGENCY_BLOCK_PERCENTAGE = 95;
22026
22470
  var FORWARD_PRESSURE_LIMIT_FACTOR = 0.85;
22027
22471
  function applyForwardPressureFloor(trailingPercentage, trailingInputTokens, piUsageTokens, correctedLimit) {
@@ -22643,8 +23087,8 @@ function parsePiFallbackToolOwnerId(ownerMsgId) {
22643
23087
  return { timestamp, role: match[2] ?? "" };
22644
23088
  }
22645
23089
  function databaseIsInTransaction(db) {
22646
- const state = db;
22647
- return state.inTransaction === true || state.isTransaction === true;
23090
+ const state2 = db;
23091
+ return state2.inTransaction === true || state2.isTransaction === true;
22648
23092
  }
22649
23093
  function runImmediateTransaction(db, fn) {
22650
23094
  if (databaseIsInTransaction(db)) {
@@ -22770,7 +23214,7 @@ function registerPiContextHandler(pi, baseOptions) {
22770
23214
  const options = baseOptions.resolveForProject?.(projectDirectory) ?? baseOptions;
22771
23215
  const schedulerConfig = options.scheduler ?? DEFAULT_SCHEDULER_CONFIG;
22772
23216
  const scheduler = schedulerFor(options);
22773
- const projectIdentity = resolveProjectIdentityForSession(projectDirectory) ?? "";
23217
+ const projectIdentity = resolveProjectIdentityForSession(projectDirectory, options.allowHomeProject) ?? "";
22774
23218
  updateSessionProjectTracking(sessionId, projectIdentity, options.db);
22775
23219
  logTransformTiming(sessionId, "findSessionId", tFindSession, `messages=${event.messages.length}`);
22776
23220
  const tEntryBranch = performance.now();
@@ -22792,7 +23236,7 @@ function registerPiContextHandler(pi, baseOptions) {
22792
23236
  const alignedEntryIds = branchLookup?.alignedEntryIds ?? null;
22793
23237
  const resolvedEntryIds = alignedEntryIds?.length === event.messages.length ? alignedEntryIds : branchEntries === null ? null : collectMessageEntryIdsByRef(ctx, event.messages, sessionId, branchEntries);
22794
23238
  const strictEntryIds = resolvedEntryIds ? [...resolvedEntryIds] : null;
22795
- if (strictEntryIds && options.injection) {
23239
+ if (strictEntryIds && options.injection && !options.compactionOff) {
22796
23240
  const removed = trimPiMessagesToCachedBoundary(options.db, sessionId, event.messages, strictEntryIds);
22797
23241
  if (removed > 0) {
22798
23242
  logTransformTiming(sessionId, "cachedBoundaryEarlyTrim", tEntryBranch, `removed=${removed}`);
@@ -22840,10 +23284,34 @@ function registerPiContextHandler(pi, baseOptions) {
22840
23284
  const tMeta = performance.now();
22841
23285
  const sessionMetaForUsage = getOrCreateSessionMeta(options.db, sessionId);
22842
23286
  logTransformTiming(sessionId, "getOrCreateSessionMeta", tMeta);
22843
- const usageReset = {
22844
- lastContextPercentage: 0,
22845
- lastInputTokens: 0,
22846
- observedSafeInputTokens: 0,
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
+ }
23311
+ const usageReset = {
23312
+ lastContextPercentage: 0,
23313
+ lastInputTokens: 0,
23314
+ observedSafeInputTokens: 0,
22847
23315
  cacheAlertSent: false
22848
23316
  };
22849
23317
  if (modelChanged) {
@@ -22883,12 +23351,14 @@ function registerPiContextHandler(pi, baseOptions) {
22883
23351
  usageInputTokens = typeof piUsage?.tokens === "number" ? piUsage.tokens : 0;
22884
23352
  }
22885
23353
  let usageContextLimit = isSaneLimit(piUsage?.contextWindow) ? piUsage.contextWindow : undefined;
23354
+ let detectedContextLimit;
22886
23355
  const tEmergencyRecovery = performance.now();
22887
23356
  let needsEmergencyBump = false;
22888
23357
  let emergencyRecoveryArmed = false;
22889
23358
  try {
22890
- const overflowState = getOverflowState(options.db, sessionId);
23359
+ const overflowState = options.compactionOff ? { detectedContextLimit: 0, needsEmergencyRecovery: false } : getOverflowState(options.db, sessionId);
22891
23360
  if (overflowState.detectedContextLimit > 0) {
23361
+ detectedContextLimit = overflowState.detectedContextLimit;
22892
23362
  usageContextLimit = Math.min(usageContextLimit ?? overflowState.detectedContextLimit, overflowState.detectedContextLimit);
22893
23363
  }
22894
23364
  emergencyRecoveryArmed = overflowState.needsEmergencyRecovery;
@@ -22906,6 +23376,17 @@ function registerPiContextHandler(pi, baseOptions) {
22906
23376
  usageContextLimit = modelWindow;
22907
23377
  }
22908
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);
22909
23390
  if (!usedPersistedUsage && isSaneLimit(usageContextLimit) && usageInputTokens > 0) {
22910
23391
  usagePercentage = usageInputTokens / usageContextLimit * 100;
22911
23392
  }
@@ -22926,7 +23407,7 @@ function registerPiContextHandler(pi, baseOptions) {
22926
23407
  logTransformTiming(sessionId, "schedulerAndUsage", tScheduler);
22927
23408
  const piMessageCount = fullWireMessageCount;
22928
23409
  const looksLikeImportedSession = schedulerDecision === "defer" && usagePercentage === 0 && sessionMeta.lastResponseTime === 0 && piMessageCount >= 50;
22929
- if (looksLikeImportedSession) {
23410
+ if (looksLikeImportedSession && !options.compactionOff) {
22930
23411
  schedulerDecision = "execute";
22931
23412
  sessionLog(sessionId, `transform: large imported session detected (${piMessageCount} messages, no usage baseline) — forcing execute on first pass`);
22932
23413
  }
@@ -22937,7 +23418,7 @@ function registerPiContextHandler(pi, baseOptions) {
22937
23418
  if (storedStableIdScheme < PI_STABLE_ID_SCHEME && !realEntryIdsAvailable) {
22938
23419
  sessionLog(sessionId, `stable-id scheme cutover deferred: real SessionEntry ids unavailable this pass (branch resolution failed) — will retry when getBranch() succeeds`);
22939
23420
  }
22940
- if (stableIdSchemeCutover) {
23421
+ if (stableIdSchemeCutover && !options.compactionOff) {
22941
23422
  schedulerDecision = "execute";
22942
23423
  signalPiPendingMaterialization(sessionId);
22943
23424
  sessionLog(sessionId, `stable-id scheme cutover: stored=${storedStableIdScheme} < current=${PI_STABLE_ID_SCHEME} — forcing execute+materialize this pass`);
@@ -22949,14 +23430,18 @@ function registerPiContextHandler(pi, baseOptions) {
22949
23430
  contextUsage: { percentage: usagePercentage },
22950
23431
  sessionMeta,
22951
23432
  historyRefreshSessions,
22952
- sessionId
23433
+ sessionId,
23434
+ effectiveExecuteThresholdPercentage
22953
23435
  });
22954
- const { midTurnAdjustedSchedulerDecision, sideEffect } = applyMidTurnDeferral({
23436
+ const { midTurnAdjustedSchedulerDecision, sideEffect } = options.compactionOff ? {
23437
+ midTurnAdjustedSchedulerDecision: "defer",
23438
+ sideEffect: "none"
23439
+ } : applyMidTurnDeferral({
22955
23440
  base: schedulerDecisionEarly,
22956
23441
  bypassReason,
22957
23442
  midTurn
22958
23443
  });
22959
- if (sideEffect === "set-flag") {
23444
+ if (sideEffect === "set-flag" && !options.compactionOff) {
22960
23445
  const flagPayload = {
22961
23446
  id: crypto3.randomUUID(),
22962
23447
  reason: `${schedulerDecisionEarly}-${bypassReason}`,
@@ -22966,8 +23451,8 @@ function registerPiContextHandler(pi, baseOptions) {
22966
23451
  }
22967
23452
  schedulerDecision = midTurnAdjustedSchedulerDecision;
22968
23453
  sessionLog(sessionId, `[boundary-exec] base=${schedulerDecisionEarly} bypass=${bypassReason} midTurn=${midTurn} effective=${midTurnAdjustedSchedulerDecision} sideEffect=${sideEffect}`);
22969
- const forceMaterialization = usagePercentage >= FORCE_MATERIALIZATION_PERCENTAGE;
22970
- const isEmergency = usagePercentage >= EMERGENCY_BLOCK_PERCENTAGE;
23454
+ const forceMaterialization = !options.compactionOff && usagePercentage >= forceMaterializationPercentage;
23455
+ const isEmergency = !options.compactionOff && usagePercentage >= EMERGENCY_BLOCK_PERCENTAGE;
22971
23456
  if (isEmergency) {
22972
23457
  const lastNotifiedAt = lastEmergencyNotificationAtMs.get(sessionId) ?? 0;
22973
23458
  const now = Date.now();
@@ -22983,7 +23468,7 @@ function registerPiContextHandler(pi, baseOptions) {
22983
23468
  sessionLog(sessionId, "EMERGENCY: historian wait completed (or timed out)");
22984
23469
  } catch {}
22985
23470
  }
22986
- 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)) {
22987
23472
  try {
22988
23473
  clearEmergencyRecovery(options.db, sessionId);
22989
23474
  sessionLog(sessionId, "EMERGENCY: disarming recovery — no eligible pre-tail history to compact (would otherwise loop at 95%)");
@@ -23035,6 +23520,7 @@ function registerPiContextHandler(pi, baseOptions) {
23035
23520
  stableIdSchemeCutover,
23036
23521
  schedulerDecision,
23037
23522
  forceMaterialization: forceMaterialization || isEmergency,
23523
+ forceMaterializationPercentage,
23038
23524
  contextUsage: {
23039
23525
  percentage: usagePercentage,
23040
23526
  inputTokens: usageInputTokens
@@ -23047,7 +23533,8 @@ function registerPiContextHandler(pi, baseOptions) {
23047
23533
  temporalAwareness: options.injection?.temporalAwareness === true,
23048
23534
  appendCompaction: resolvePiAppendCompaction(ctx),
23049
23535
  readBranchEntries: resolvePiReadBranchEntries(ctx),
23050
- isSubagent: sessionMeta.isSubagent
23536
+ isSubagent: sessionMeta.isSubagent,
23537
+ compactionOff: options.compactionOff === true
23051
23538
  });
23052
23539
  logTransformTiming(sessionId, "runPipeline", tRunPipeline);
23053
23540
  const postPipelineStart = performance.now();
@@ -23071,7 +23558,7 @@ function registerPiContextHandler(pi, baseOptions) {
23071
23558
  }
23072
23559
  logTransformTiming(sessionId, "transformDecisionAndReuseState", tTransformDecision);
23073
23560
  const tHistorianScheduling = performance.now();
23074
- if (options.historian) {
23561
+ if (options.historian && !options.compactionOff) {
23075
23562
  maybeFireHistorian({
23076
23563
  pi,
23077
23564
  ctx,
@@ -23089,22 +23576,24 @@ function registerPiContextHandler(pi, baseOptions) {
23089
23576
  let outputMessages = result.messages;
23090
23577
  const tNoteNudges = performance.now();
23091
23578
  try {
23092
- outputMessages = applyNoteNudges({
23093
- sessionId,
23094
- db: options.db,
23095
- messages: outputMessages,
23096
- projectIdentity,
23097
- entryIds: strictEntryIds,
23098
- entryIdByRef: result.postCommitEntryIdByRef,
23099
- isCacheBusting: isCacheBusting || result.executedWorkThisPass,
23100
- syntheticLeadingCount: result.syntheticLeadingCount
23101
- });
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
+ }
23102
23591
  } catch (err) {
23103
23592
  sessionLog(sessionId, `note nudges failed: ${err instanceof Error ? err.message : String(err)}`);
23104
23593
  }
23105
23594
  logTransformTiming(sessionId, "noteNudges", tNoteNudges);
23106
23595
  const tAutoSearch = performance.now();
23107
- if (options.autoSearch?.enabled) {
23596
+ if (options.autoSearch?.enabled && !options.compactionOff) {
23108
23597
  try {
23109
23598
  outputMessages = await runAutoSearchHintForPi({
23110
23599
  sessionId,
@@ -23129,7 +23618,7 @@ function registerPiContextHandler(pi, baseOptions) {
23129
23618
  const tTodoCapture = performance.now();
23130
23619
  try {
23131
23620
  const sessionMetaForTodo = getOrCreateSessionMeta(options.db, sessionId);
23132
- if (!sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
23621
+ if (!options.compactionOff && !sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
23133
23622
  const isCacheBustingForTodo = isCacheBusting || result.executedWorkThisPass;
23134
23623
  outputMessages = injectSyntheticTodowriteForPi({
23135
23624
  db: options.db,
@@ -23147,7 +23636,7 @@ function registerPiContextHandler(pi, baseOptions) {
23147
23636
  const tChannelAccounting = performance.now();
23148
23637
  try {
23149
23638
  const sessionMetaForCh1 = getOrCreateSessionMeta(options.db, sessionId);
23150
- if (!sessionMetaForCh1.isSubagent) {
23639
+ if (!options.compactionOff && !sessionMetaForCh1.isSubagent) {
23151
23640
  const resolvedExecuteThresholdPct = resolveExecuteThreshold(schedulerConfig.executeThresholdPercentage ?? 65, liveModelBySession.get(sessionId), 65, {
23152
23641
  tokensConfig: schedulerConfig.executeThresholdTokens,
23153
23642
  contextLimit: usageContextLimit ?? 0
@@ -23214,7 +23703,7 @@ function registerPiContextHandler(pi, baseOptions) {
23214
23703
  }
23215
23704
  logTransformTiming(sessionId, "workMetrics", tWorkMetrics);
23216
23705
  const tStableIdSchemePersist = performance.now();
23217
- if (stableIdSchemeCutover) {
23706
+ if (stableIdSchemeCutover && !options.compactionOff) {
23218
23707
  persistStableIdSchemeForRun(options.db, sessionId, {
23219
23708
  piStableIdScheme: PI_STABLE_ID_SCHEME
23220
23709
  });
@@ -23239,7 +23728,7 @@ function registerPiContextHandler(pi, baseOptions) {
23239
23728
  sessionLog(sessionId, `transform completed in ${transformElapsedMs.toFixed(1)}ms (${outputMessages.length} messages, ${result.targetCount} targets, watermark: ${result.reasoningWatermark})`);
23240
23729
  return { messages: outputMessages };
23241
23730
  } catch (err) {
23242
- if (isFailClosedBlockingError(err))
23731
+ if (isFailClosedBlockingError(err) && !baseOptions.compactionOff)
23243
23732
  throw err;
23244
23733
  const message = err instanceof Error ? err.message : String(err);
23245
23734
  const stack = err instanceof Error ? err.stack : undefined;
@@ -23396,6 +23885,7 @@ function spawnPiHistorianRun(args) {
23396
23885
  twoPass: historian.twoPass,
23397
23886
  thinkingLevel: historian.thinkingLevel,
23398
23887
  memoryEnabled: historian.memoryEnabled,
23888
+ allowHomeProject: historian.allowHomeProject,
23399
23889
  autoPromote: historian.autoPromote,
23400
23890
  userMemoriesEnabled: historian.userMemoriesEnabled,
23401
23891
  language: historian.language,
@@ -23475,15 +23965,22 @@ function maybeFireHistorian(args) {
23475
23965
  const piUsage = ctx.getContextUsage?.();
23476
23966
  let usageSource;
23477
23967
  usageContextLimit = isSaneLimit(piUsage?.contextWindow) ? piUsage.contextWindow : undefined;
23968
+ let detectedContextLimit;
23478
23969
  if (usageContextLimit === undefined && isSaneLimit(ctx.model?.contextWindow)) {
23479
23970
  usageContextLimit = ctx.model.contextWindow;
23480
23971
  }
23481
23972
  try {
23482
23973
  const overflowState = getOverflowState(db, sessionId);
23483
23974
  if (overflowState.detectedContextLimit > 0) {
23975
+ detectedContextLimit = overflowState.detectedContextLimit;
23484
23976
  usageContextLimit = Math.min(usageContextLimit ?? overflowState.detectedContextLimit, overflowState.detectedContextLimit);
23485
23977
  }
23486
23978
  } catch {}
23979
+ usageContextLimit = resolvePiUsableContextLimit({
23980
+ rawContextWindow: usageContextLimit,
23981
+ model: ctx.model,
23982
+ detectedContextLimit
23983
+ });
23487
23984
  const sessionMetaForUsage = getOrCreateSessionMeta(db, sessionId);
23488
23985
  if (sessionMetaForUsage.lastContextPercentage > 0 && sessionMetaForUsage.lastInputTokens > 0) {
23489
23986
  usage = {
@@ -23522,6 +24019,7 @@ function maybeFireHistorian(args) {
23522
24019
  modelKey,
23523
24020
  usageContextLimit
23524
24021
  });
24022
+ const historianForceMaterializationPercentage = escalationBands(triggerInputs.executeThresholdPercentage).forceMaterializationPercentage;
23525
24023
  const boundaryContextLimit = triggerInputs.contextLimit;
23526
24024
  const resolvePiBoundarySnapshot = (emergencyTailScale) => resolveProtectedTailBoundary(resolveBoundaryContext({
23527
24025
  db,
@@ -23537,7 +24035,7 @@ function maybeFireHistorian(args) {
23537
24035
  }));
23538
24036
  const resolveRunnablePiBoundarySnapshot = () => {
23539
24037
  let snapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot());
23540
- if (!hasRunnableCompartmentWindow(snapshot) && usage.percentage >= 80) {
24038
+ if (!hasRunnableCompartmentWindow(snapshot) && usage.percentage >= historianForceMaterializationPercentage) {
23541
24039
  snapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot(usage.percentage >= 95 ? 0.25 : 0.5));
23542
24040
  }
23543
24041
  return snapshot;
@@ -23581,15 +24079,15 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
23581
24079
  const trigger = checkCompartmentTrigger(db, sessionId, sessionMeta, usage, 0, triggerInputs.executeThresholdPercentage, triggerInputs.triggerBudget, triggerInputs.clearReasoningAge, triggerInputs.commitClusterTrigger, args.activeTags, boundaryContextLimit, () => {
23582
24080
  const messages = provider.readMessages();
23583
24081
  return { messages, absoluteMessageCount: messages.length };
23584
- }, args.taggerFloor);
24082
+ }, args.taggerFloor, { canClearReasoning: true });
23585
24083
  if (!trigger.shouldFire) {
23586
24084
  sessionLog(sessionId, `historian trigger eval: shouldFire=false (no trigger condition met)`);
23587
24085
  try {
23588
24086
  const overflowState = getOverflowState(db, sessionId);
23589
- if (overflowState.needsEmergencyRecovery && usage.percentage < FORCE_MATERIALIZATION_PERCENTAGE && !inFlightHistorian.has(sessionId)) {
24087
+ if (overflowState.needsEmergencyRecovery && usage.percentage < historianForceMaterializationPercentage && !inFlightHistorian.has(sessionId)) {
23590
24088
  boundarySnapshot ??= resolveRunnablePiBoundarySnapshot();
23591
24089
  }
23592
- 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)) {
23593
24091
  clearEmergencyRecovery(db, sessionId);
23594
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)`);
23595
24093
  }
@@ -23677,7 +24175,45 @@ function captureReasoningMutationRollback(messages) {
23677
24175
  }
23678
24176
  };
23679
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
+ }
23680
24213
  async function runPipeline(args) {
24214
+ if (args.compactionOff)
24215
+ return runCompactionOffPipeline(args);
24216
+ const forceMaterializationPercentage = args.forceMaterializationPercentage ?? escalationBands(65).forceMaterializationPercentage;
23681
24217
  let executedWorkThisPass = false;
23682
24218
  let historyWasConsumedThisPass = false;
23683
24219
  let materializationSatisfiedThisPass = false;
@@ -23716,7 +24252,7 @@ async function runPipeline(args) {
23716
24252
  })();
23717
24253
  const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
23718
24254
  const ctxReduceCallable = !args.sessionMeta.isSubagent;
23719
- 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;
23720
24256
  const deferredMaterializeEligible = canConsumeDeferredLate && deferredMaterializationSessions.has(args.sessionId);
23721
24257
  const piHardSignals = args.injection ? (() => {
23722
24258
  const hardMeta = args.sessionMeta;
@@ -23966,7 +24502,7 @@ async function runPipeline(args) {
23966
24502
  }
23967
24503
  const toolReclaimExecutePass = args.schedulerDecision === "execute";
23968
24504
  const alreadyMutatingThisPass = pendingOpsDidMutate || heuristicOrReasoningDidMutate;
23969
- const emergencyDropEligible = args.forceMaterialization === true || args.contextUsage.percentage >= FORCE_MATERIALIZATION_PERCENTAGE;
24505
+ const emergencyDropEligible = args.forceMaterialization === true || args.contextUsage.percentage >= forceMaterializationPercentage;
23970
24506
  let autoReclaimTargetCount = 0;
23971
24507
  let autoReclaimDidMutate = false;
23972
24508
  if (toolReclaimExecutePass && alreadyMutatingThisPass && !emergencyDropEligible) {
@@ -24353,6 +24889,13 @@ function appendReminderToPiUserMessage(message, reminder) {
24353
24889
  }
24354
24890
  contentArr.push({ type: "text", text: reminder.trimStart() });
24355
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
+ }
24356
24899
  function clearContextHandlerSession(sessionId) {
24357
24900
  invalidateTrueRawTokenCache({ sessionId, reason: "pi.branch.changed" });
24358
24901
  activeContextHandlerSessions.delete(sessionId);
@@ -24411,6 +24954,14 @@ No active Pi session is available.`,
24411
24954
  });
24412
24955
  return;
24413
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
+ }
24414
24965
  const pendingBefore = getPendingOps(deps.db, sessionId).length;
24415
24966
  const result = executeFlush(deps.db, sessionId);
24416
24967
  signalPiHistoryRefresh(sessionId);
@@ -24455,6 +25006,24 @@ function historianResponseDumpDir(directory) {
24455
25006
  return getProjectMagicContextHistorianDir(directory);
24456
25007
  }
24457
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
+ }
24458
25027
  async function runValidatedHistorianPass(args) {
24459
25028
  const firstRun = await runHistorianPrompt({
24460
25029
  ...args,
@@ -24520,6 +25089,7 @@ async function runEditorPassOrFallback(args) {
24520
25089
  sessionLog(args.parentSessionId, "historian two-pass: running editor on draft");
24521
25090
  const editorRun = await runHistorianPrompt({
24522
25091
  client: args.client,
25092
+ db: args.db,
24523
25093
  parentSessionId: args.parentSessionId,
24524
25094
  sessionDirectory: args.sessionDirectory,
24525
25095
  prompt: buildHistorianEditorPrompt(args.draftXml),
@@ -24546,6 +25116,7 @@ async function runEditorPassOrFallback(args) {
24546
25116
  async function runHistorianPrompt(args) {
24547
25117
  const {
24548
25118
  client,
25119
+ db,
24549
25120
  parentSessionId,
24550
25121
  sessionDirectory,
24551
25122
  prompt,
@@ -24579,12 +25150,12 @@ async function runHistorianPrompt(args) {
24579
25150
  };
24580
25151
  try {
24581
25152
  sessionLog(parentSessionId, `historian: creating child session (agent=${agentId}, model=${modelOverride ? `${modelOverride.providerID}/${modelOverride.modelID}` : `agent:${agentId}`})`);
24582
- const createResponse = await client.session.create({
24583
- body: {
24584
- parentID: parentSessionId,
24585
- title: "magic-context-compartment"
24586
- },
24587
- query: { directory: sessionDirectory }
25153
+ const createResponse = await createChildSessionWithFence({
25154
+ client,
25155
+ db,
25156
+ parentSessionId,
25157
+ title: "magic-context-compartment",
25158
+ directory: sessionDirectory
24588
25159
  });
24589
25160
  const createdSession = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
24590
25161
  agentSessionId = typeof createdSession?.id === "string" ? createdSession.id : null;
@@ -24632,7 +25203,18 @@ async function runHistorianPrompt(args) {
24632
25203
  preferResponseOnMissingData: true
24633
25204
  });
24634
25205
  const invocationId = recordInvocation5({ status: "completed", messages });
24635
- 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;
24636
25218
  if (!result) {
24637
25219
  return {
24638
25220
  ok: false,
@@ -24684,6 +25266,7 @@ async function runFallbackHistorianPass(args) {
24684
25266
  args.callbacks?.onModelFallback?.(modelId, i + 1, chain.length);
24685
25267
  const fallbackRun = await runHistorianPrompt({
24686
25268
  client: args.client,
25269
+ db: args.db,
24687
25270
  parentSessionId: args.parentSessionId,
24688
25271
  sessionDirectory: args.sessionDirectory,
24689
25272
  prompt: args.prompt,
@@ -24779,116 +25362,6 @@ function sanitizeDumpName(value) {
24779
25362
  return value.replace(/[^a-zA-Z0-9._-]/g, "-");
24780
25363
  }
24781
25364
 
24782
- // ../plugin/src/hooks/magic-context/send-session-notification.ts
24783
- function hasNotificationSessionClient(client) {
24784
- if (client === null || typeof client !== "object")
24785
- return false;
24786
- const candidate = client;
24787
- if (candidate.session === undefined)
24788
- return true;
24789
- if (candidate.session === null || typeof candidate.session !== "object")
24790
- return false;
24791
- const session = candidate.session;
24792
- return (session.prompt === undefined || typeof session.prompt === "function") && (session.promptAsync === undefined || typeof session.promptAsync === "function");
24793
- }
24794
- function inferToastVariant(text) {
24795
- const lower = text.toLowerCase();
24796
- if (lower.includes("error") || lower.includes("failed") || lower.includes("alert"))
24797
- return "error";
24798
- if (lower.includes("warning") || lower.includes("⚠"))
24799
- return "warning";
24800
- if (lower.includes("complete") || lower.includes("success") || lower.includes("✓") || lower.includes("finished"))
24801
- return "success";
24802
- return "info";
24803
- }
24804
- function extractToastTitle(text) {
24805
- const headingMatch = text.match(/^#+\s+(.+)/m);
24806
- if (headingMatch)
24807
- return headingMatch[1].trim();
24808
- const firstLine = text.split(`
24809
- `)[0].trim();
24810
- if (firstLine.length <= 80)
24811
- return firstLine;
24812
- return "Magic Context";
24813
- }
24814
- async function sendIgnoredMessage(client, sessionId, text, params, forcePersist = false) {
24815
- const title = extractToastTitle(text);
24816
- const message = text.length > 200 ? `${text.slice(0, 200)}…` : text;
24817
- const toastVariant = inferToastVariant(text);
24818
- const duration = params.toastDurationMs ?? 5000;
24819
- const { isTuiConnected: checkTui } = await import("./rpc-notifications-td0amw6w.js");
24820
- if (!forcePersist && checkTui(sessionId)) {
24821
- try {
24822
- const { pushNotification } = await import("./rpc-notifications-td0amw6w.js");
24823
- pushNotification("toast", {
24824
- title,
24825
- message,
24826
- variant: toastVariant,
24827
- duration
24828
- }, sessionId);
24829
- return "sent";
24830
- } catch {
24831
- sessionLog(sessionId, "TUI RPC toast enqueue failed, falling back to ignored message");
24832
- }
24833
- }
24834
- const { waitForSafeNotificationTarget } = await import("./safe-notification-target-ng8ygena.js");
24835
- if (await waitForSafeNotificationTarget(client, sessionId) === "skip") {
24836
- sessionLog(sessionId, "notification skipped (session not titled yet)");
24837
- return "skipped";
24838
- }
24839
- if (!hasNotificationSessionClient(client)) {
24840
- sessionLog(sessionId, "session prompt API unavailable for notification");
24841
- return "failed";
24842
- }
24843
- const c = client;
24844
- let agent = params.agent || undefined;
24845
- let variant = params.variant || undefined;
24846
- let model = params.providerId && params.modelId ? { providerID: params.providerId, modelID: params.modelId } : undefined;
24847
- if (!agent || !model || !variant) {
24848
- try {
24849
- const { resolvePromptContext } = await import("./prompt-context-602yq94t.js");
24850
- const resolved = await resolvePromptContext(client, sessionId);
24851
- if (resolved) {
24852
- agent = agent ?? resolved.agent;
24853
- model = model ?? resolved.model;
24854
- variant = variant ?? resolved.variant;
24855
- }
24856
- } catch {}
24857
- }
24858
- const input = {
24859
- path: { id: sessionId },
24860
- body: {
24861
- noReply: true,
24862
- agent,
24863
- model,
24864
- variant,
24865
- parts: [
24866
- {
24867
- type: "text",
24868
- text,
24869
- ignored: true
24870
- }
24871
- ]
24872
- }
24873
- };
24874
- try {
24875
- if (typeof c.session?.prompt === "function") {
24876
- await Promise.resolve(c.session.prompt(input));
24877
- return "sent";
24878
- }
24879
- if (typeof c.session?.promptAsync === "function") {
24880
- await c.session.promptAsync(input);
24881
- return "sent";
24882
- }
24883
- sessionLog(sessionId, "session prompt API unavailable for notification");
24884
- return "failed";
24885
- } catch (error) {
24886
- const msg = getErrorMessage(error);
24887
- sessionLog(sessionId, "failed to send notification:", msg);
24888
- return "failed";
24889
- }
24890
- }
24891
-
24892
25365
  // ../plugin/src/hooks/magic-context/compartment-runner-incremental.ts
24893
25366
  var HISTORIAN_ALERT_COOLDOWN_MS2 = 60 * 1000;
24894
25367
  var lastHistorianAlertBySession2 = new Map;
@@ -25106,6 +25579,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
25106
25579
  emitProgress(`Running historian (pass ${passCount + 1})…`);
25107
25580
  const validatedPass = await runValidatedHistorianPass({
25108
25581
  client,
25582
+ db,
25109
25583
  parentSessionId: sessionId,
25110
25584
  sessionDirectory,
25111
25585
  prompt,
@@ -25525,6 +25999,7 @@ ${chunk.text}`,
25525
25999
  Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${chunk.startIndex}-${chunk.endIndex}.`, notifParams());
25526
26000
  const validatedPass = await runValidatedHistorianPass({
25527
26001
  client,
26002
+ db,
25528
26003
  parentSessionId: sessionId,
25529
26004
  sessionDirectory,
25530
26005
  prompt,
@@ -25880,8 +26355,8 @@ function renderStatusText(ctx, db, sessionId) {
25880
26355
  const inputTokens = typeof usage?.tokens === "number" ? usage.tokens : undefined;
25881
26356
  const pct = typeof usage?.percent === "number" ? usage.percent : undefined;
25882
26357
  const meta = readSessionMetaStatus(db, sessionId);
25883
- const state = renderHistorianState(meta, recompSessions.has(sessionId));
25884
- 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}`;
25885
26360
  }
25886
26361
  function renderHistorianState(meta, recompActive) {
25887
26362
  const failureCount = meta?.historian_failure_count ?? 0;
@@ -25984,6 +26459,14 @@ No active Pi session is available.`,
25984
26459
  return;
25985
26460
  }
25986
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
+ }
25987
26470
  const parsed = parseRecompArgs(args);
25988
26471
  if (parsed.kind === "error") {
25989
26472
  sendCtxStatusMessage(pi, {
@@ -26336,7 +26819,7 @@ function contextualizeUpgradeReason(reason) {
26336
26819
  // src/pi-memory-migration.ts
26337
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.";
26338
26821
  async function runPiMemoryMigration(deps) {
26339
- const projectPath = resolveProjectIdentityForSession(deps.directory);
26822
+ const projectPath = resolveProjectIdentityForSession(deps.directory, deps.allowHomeProject);
26340
26823
  if (!projectPath) {
26341
26824
  return {
26342
26825
  ran: false,
@@ -26451,6 +26934,14 @@ No active Pi session is available.`,
26451
26934
  return;
26452
26935
  }
26453
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
+ }
26454
26945
  if (!currentDeps.historianModel) {
26455
26946
  sendCtxStatusMessage(pi, {
26456
26947
  title: "/ctx-session-upgrade",
@@ -26497,6 +26988,7 @@ An upgrade or recomp is already running for this session in the background. Wait
26497
26988
  timeoutMs: currentDeps.historianTimeoutMs,
26498
26989
  thinkingLevel: currentDeps.historianThinkingLevel,
26499
26990
  directory: ctx.cwd,
26991
+ allowHomeProject: currentDeps.allowHomeProject,
26500
26992
  sessionId,
26501
26993
  userMemoriesEnabled: currentDeps.userMemoriesEnabled,
26502
26994
  language: currentDeps.language
@@ -26507,7 +26999,7 @@ An upgrade or recomp is already running for this session in the background. Wait
26507
26999
  }
26508
27000
  };
26509
27001
  if (upgradableCount === 0) {
26510
- const projectPath = resolveProjectIdentityForSession(ctx.cwd);
27002
+ const projectPath = resolveProjectIdentityForSession(ctx.cwd, currentDeps.allowHomeProject);
26511
27003
  if (!projectPath)
26512
27004
  return;
26513
27005
  const migrationPending = migrationEnabled && !isMemoryMigrationDone(currentDeps.db, projectPath);
@@ -26728,8 +27220,8 @@ function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageC
26728
27220
  "### Cache TTL",
26729
27221
  `- Configured: ${meta.cacheTtl}`,
26730
27222
  `- Last response: ${meta.lastResponseTime > 0 ? `${Math.round(elapsed / 1000)}s ago` : "never"}`,
26731
- `- Remaining: ${cacheExpired ? "expired" : `${Math.round(remainingMs / 1000)}s`}`,
26732
- `- 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}%`}`,
26733
27225
  "",
26734
27226
  "### Execute Threshold",
26735
27227
  `- Execute threshold: ${formatExecuteThreshold(thresholdDetail, displayContextLimit)}`,
@@ -26834,7 +27326,7 @@ ${c.content}
26834
27326
  // package.json
26835
27327
  var package_default = {
26836
27328
  name: "@cortexkit/pi-magic-context",
26837
- version: "0.33.1",
27329
+ version: "0.34.0",
26838
27330
  type: "module",
26839
27331
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
26840
27332
  main: "dist/index.js",
@@ -27004,7 +27496,7 @@ function renderInner(s, theme, innerWidth) {
27004
27496
  lines.push(`Upgrade: ${theme.fg("accent", "up to date")}`);
27005
27497
  }
27006
27498
  lines.push(`Pending drops: ${s.pendingOpsCount}`);
27007
- 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`}`);
27008
27500
  lines.push("");
27009
27501
  lines.push(theme.fg("muted", "Tags"));
27010
27502
  lines.push(`Active ${s.activeTags} (~${formatBytes(s.activeBytes)}) · Dropped ${s.droppedTags} · Total ${s.totalTags}`);
@@ -27046,8 +27538,18 @@ function buildPiStatusDetail(pi, ctx, deps, sessionId) {
27046
27538
  const usage = ctx.getContextUsage?.();
27047
27539
  const meta = getOrCreateSessionMeta(deps.db, sessionId);
27048
27540
  const inputTokens = typeof usage?.tokens === "number" ? usage.tokens : meta.lastInputTokens;
27049
- const usagePercentage = typeof usage?.percent === "number" ? usage.percent : meta.lastContextPercentage;
27050
- 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;
27051
27553
  const compartments = getCompartments(deps.db, sessionId);
27052
27554
  const metaRow = readSessionMetaRow(deps.db, sessionId);
27053
27555
  const memoryBlockCount = Number(metaRow?.memory_block_count ?? 0);
@@ -27095,9 +27597,15 @@ ${safeStringify(tool.parameters)}`);
27095
27597
  sessionId
27096
27598
  });
27097
27599
  const cacheTtl = meta.cacheTtl || "5m";
27098
- 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;
27099
27607
  const elapsed = meta.lastResponseTime > 0 ? Date.now() - meta.lastResponseTime : 0;
27100
- 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;
27101
27609
  const cacheExpired = meta.lastResponseTime > 0 && cacheRemainingMs === 0;
27102
27610
  const historyBlockTokens = compartmentTokens + factTokens;
27103
27611
  const historyBudgetPercentage = deps.historyBudgetPercentage ?? 0.15;
@@ -27252,22 +27760,6 @@ function readPendingOpsCount(db, sessionId) {
27252
27760
  return 0;
27253
27761
  }
27254
27762
  }
27255
- function parseTtlString(ttl) {
27256
- const match = ttl.match(/^(\d+)(s|m|h)$/);
27257
- if (!match)
27258
- return 5 * 60 * 1000;
27259
- const val = Number.parseInt(match[1] ?? "5", 10);
27260
- switch (match[2]) {
27261
- case "s":
27262
- return val * 1000;
27263
- case "m":
27264
- return val * 60 * 1000;
27265
- case "h":
27266
- return val * 3600 * 1000;
27267
- default:
27268
- return 5 * 60 * 1000;
27269
- }
27270
- }
27271
27763
  function safeRead(fn, fallback) {
27272
27764
  try {
27273
27765
  return fn();
@@ -27331,7 +27823,18 @@ No active Pi session is available.`,
27331
27823
  }
27332
27824
  const usage = ctx.getContextUsage?.();
27333
27825
  const modelKey = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
27334
- 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);
27335
27838
  const details = buildStatusDetails(currentDeps, sessionId);
27336
27839
  sendCtxStatusMessage(pi, { title: "/ctx-status", text: statusText, level: "info" }, details);
27337
27840
  } catch (error) {
@@ -27433,6 +27936,14 @@ No active Pi session is available.`,
27433
27936
  return;
27434
27937
  }
27435
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
+ }
27436
27947
  const sessionMeta = getOrCreateSessionMeta(currentDeps.db, sessionId);
27437
27948
  if (sessionMeta.isSubagent) {
27438
27949
  sendCtxStatusMessage(pi, {
@@ -27485,7 +27996,7 @@ async function runPiWrapup(pi, deps, ctx, sessionId, messagesToKeep) {
27485
27996
  const unregister = setRawMessageProvider(sessionId, provider);
27486
27997
  let holderId = "";
27487
27998
  try {
27488
- const contextLimit = resolvePiContextLimit(ctx);
27999
+ const contextLimit = resolvePiContextLimit(ctx, deps.db, sessionId);
27489
28000
  const modelKey = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
27490
28001
  const executeThresholdPercentage = resolveExecuteThreshold(deps.executeThresholdPercentage ?? 65, modelKey, 65, {
27491
28002
  tokensConfig: deps.executeThresholdTokens,
@@ -27714,15 +28225,19 @@ Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments.
27714
28225
  unregister();
27715
28226
  }
27716
28227
  }
27717
- function resolvePiContextLimit(ctx) {
28228
+ function resolvePiContextLimit(ctx, db, sessionId) {
27718
28229
  const usage = ctx.getContextUsage?.();
27719
- if (typeof usage?.contextWindow === "number" && usage.contextWindow > 0) {
27720
- return usage.contextWindow;
27721
- }
27722
- if (typeof ctx.model?.contextWindow === "number" && ctx.model.contextWindow > 0) {
27723
- return ctx.model.contextWindow;
27724
- }
27725
- 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;
27726
28241
  }
27727
28242
  async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker, maxWaitMs) {
27728
28243
  const waitStartedAt = Date.now();
@@ -27742,15 +28257,15 @@ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarke
27742
28257
  function estimateChunks(tokens, chunkTokens) {
27743
28258
  return Math.max(1, Math.ceil(Math.max(0, tokens) / Math.max(1, chunkTokens)));
27744
28259
  }
27745
- function formatExistingWrapup(state) {
27746
- if (!state) {
28260
+ function formatExistingWrapup(state2) {
28261
+ if (!state2) {
27747
28262
  return `## Magic Wrapup — Skipped
27748
28263
 
27749
28264
  Another /ctx-wrapup is already compacting this session. Wait for it to finish, then try again.`;
27750
28265
  }
27751
28266
  return `## Magic Wrapup — Skipped
27752
28267
 
27753
- 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.`;
27754
28269
  }
27755
28270
  function readBranchEntries(ctx) {
27756
28271
  const getBranch = ctx.sessionManager.getBranch;
@@ -27874,15 +28389,43 @@ function stripTagPrefixFromAssistantMessage(message) {
27874
28389
 
27875
28390
  // src/index.ts
27876
28391
  var PREFIX2 = "[magic-context][pi]";
27877
- 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) {
27878
28407
  const projectDir = ctx.cwd;
27879
- const projectIdentity = resolveProjectIdentityForSession(projectDir) ?? "";
28408
+ const projectIdentity = resolveProjectIdentityForSession(projectDir, allowHomeProject) ?? "";
27880
28409
  return { projectDir, projectIdentity };
27881
28410
  }
27882
28411
  function signalPiDeferredCompactionMarkerDrain(sessionId) {
27883
28412
  signalPiDeferredHistoryRefresh(sessionId);
27884
28413
  signalPiDeferredMaterialization(sessionId);
27885
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
+ }
27886
28429
  function persistPiMessageEndModelMeta(args) {
27887
28430
  if (!args.message || typeof args.message !== "object")
27888
28431
  return;
@@ -27988,7 +28531,10 @@ var __test = {
27988
28531
  logPiConfigLoad,
27989
28532
  resetLoggedPiConfigDirs() {
27990
28533
  loggedPiConfigDirs.clear();
27991
- }
28534
+ },
28535
+ isPiMagicContextActiveInProcess,
28536
+ markPiMagicContextActive,
28537
+ clearPiMagicContextActive
27992
28538
  };
27993
28539
  function formatTokens(value) {
27994
28540
  return value.toLocaleString();
@@ -28004,23 +28550,28 @@ function getPiMessageModel(message) {
28004
28550
  };
28005
28551
  }
28006
28552
  function resolvePiPressureContextLimit(args) {
28007
- let effectiveContextLimit = isSaneLimit(args.piContextWindow) ? args.piContextWindow : 0;
28553
+ let detectedContextLimit;
28008
28554
  try {
28009
28555
  const overflowState = getOverflowState(args.db, args.sessionId);
28010
28556
  if (overflowState.detectedContextLimit > 0) {
28011
- effectiveContextLimit = effectiveContextLimit > 0 ? Math.min(effectiveContextLimit, overflowState.detectedContextLimit) : overflowState.detectedContextLimit;
28557
+ detectedContextLimit = overflowState.detectedContextLimit;
28012
28558
  }
28013
28559
  } catch (err) {
28014
28560
  warn("message_end: getOverflowState failed:", err);
28015
28561
  }
28016
- return effectiveContextLimit;
28562
+ return resolvePiUsableContextLimit({
28563
+ rawContextWindow: args.piContextWindow,
28564
+ model: args.model,
28565
+ detectedContextLimit
28566
+ }) ?? 0;
28017
28567
  }
28018
28568
  async function persistPiPressureFromMessageEnd(args) {
28019
28569
  const { provider, model } = getPiMessageModel(args.message);
28020
28570
  const effectiveContextLimit = resolvePiPressureContextLimit({
28021
28571
  db: args.db,
28022
28572
  sessionId: args.sessionId,
28023
- piContextWindow: args.piContextWindow
28573
+ piContextWindow: args.piContextWindow,
28574
+ model: args.piModel ?? { provider, id: model }
28024
28575
  });
28025
28576
  const usage = extractAssistantUsage(args.message);
28026
28577
  const pressure = computePiPressure(usage, effectiveContextLimit);
@@ -28047,8 +28598,8 @@ async function persistPiPressureFromMessageEnd(args) {
28047
28598
  }
28048
28599
  } else if (typeof args.piTokens === "number") {
28049
28600
  updates.lastInputTokens = args.piTokens;
28050
- if (args.piContextWindow > 0) {
28051
- updates.lastContextPercentage = args.piTokens / args.piContextWindow * 100;
28601
+ if (effectiveContextLimit > 0) {
28602
+ updates.lastContextPercentage = args.piTokens / effectiveContextLimit * 100;
28052
28603
  }
28053
28604
  }
28054
28605
  updateSessionMeta(args.db, args.sessionId, updates);
@@ -28075,7 +28626,8 @@ function resolveSidekickFromConfig(config) {
28075
28626
  timeoutMs: sidekick.timeout_ms,
28076
28627
  thinking_level: sidekick.thinking_level,
28077
28628
  fallbackModels: resolveFallbackChain(sidekick.fallback_models),
28078
- language: config.language
28629
+ language: config.language,
28630
+ allowHomeProject: config.allow_home_project
28079
28631
  };
28080
28632
  }
28081
28633
  function resolveHistorianFromConfig(config) {
@@ -28105,7 +28657,8 @@ function resolveHistorianFromConfig(config) {
28105
28657
  memoryEnabled: config.memory.enabled,
28106
28658
  autoPromote: config.memory.auto_promote,
28107
28659
  userMemoriesEnabled: userMemoryCollectionEnabled(config.dreamer),
28108
- language: config.language
28660
+ language: config.language,
28661
+ allowHomeProject: config.allow_home_project
28109
28662
  };
28110
28663
  }
28111
28664
  function resolveAutoSearchFromConfig(config) {
@@ -28125,7 +28678,20 @@ async function src_default(pi) {
28125
28678
  log(`${PREFIX2} subagent child detected (${MAGIC_CONTEXT_PI_SUBAGENT_ENV}=1); skipping full extension registration`);
28126
28679
  return;
28127
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();
28128
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
+ });
28129
28695
  const storageDir = getMagicContextStorageDir();
28130
28696
  const dbPath = join5(storageDir, "context.db");
28131
28697
  let db;
@@ -28144,16 +28710,26 @@ async function src_default(pi) {
28144
28710
  info("plugin DISABLED via config (enabled: false) — skipping registration");
28145
28711
  return;
28146
28712
  }
28713
+ const migration = getMigrationOnOpenRefusal();
28714
+ const blockingProcesses = migration?.serverPids.map((pid) => ({
28715
+ harness: "OpenCode server",
28716
+ pid
28717
+ })) ?? [];
28147
28718
  const fence = getSchemaFenceRejection();
28148
- 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 ? {
28149
28725
  kind: "schema_fence",
28150
28726
  persistedVersion: fence.persistedVersion,
28151
28727
  supportedVersion: fence.supportedVersion
28152
28728
  } : {
28153
28729
  kind: "storage_failure",
28154
- 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)`
28155
28731
  };
28156
- if (early.config.fail_closed_blocking === false) {
28732
+ if (early.config.fail_closed_blocking === false || !isCompactionEnabled(early.config)) {
28157
28733
  warn(`Magic Context (pi) storage unavailable at ${dbPath}: ${formatFailClosedBlockingMessage(reason)}. ` + "fail_closed_blocking=false — degrading silently (hooks not registered).");
28158
28734
  return;
28159
28735
  }
@@ -28201,26 +28777,30 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28201
28777
  })();
28202
28778
  }, 0);
28203
28779
  const projectDir = process.cwd();
28204
- const projectIdentity = resolveProjectIdentityForSession(projectDir) ?? "";
28205
28780
  const seenDreamerProjectIdentities = new Set;
28206
- if (projectIdentity)
28207
- seenDreamerProjectIdentities.add(projectIdentity);
28208
- try {
28209
- const pendingPiMarkerSessions = getSessionsWithPendingPiMarker(db);
28210
- for (const sid of pendingPiMarkerSessions) {
28211
- signalPiDeferredCompactionMarkerDrain(sid);
28212
- }
28213
- if (pendingPiMarkerSessions.length > 0) {
28214
- log(`${PREFIX2} rehydrated ${pendingPiMarkerSessions.length} Pi deferred compaction marker session(s)`);
28215
- }
28216
- } catch (err) {
28217
- warn(`Magic Context (pi) failed to rehydrate deferred Pi compaction markers: ${err instanceof Error ? err.message : String(err)}`);
28218
- }
28219
- info(`loaded v${PLUGIN_VERSION} | harness=pi | db=${dbPath} | ` + `project=${projectIdentity} | dir=${projectDir}`);
28220
28781
  ensureConfigLocationsMigrated(projectDir);
28221
28782
  const { config, warnings, loadedFromPaths } = loadPiConfig({
28222
28783
  cwd: projectDir
28223
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
+ }
28224
28804
  configurePiSubagentExtensions(config.pi?.subagent_extensions);
28225
28805
  logPiConfigLoad({
28226
28806
  dir: projectDir,
@@ -28228,6 +28808,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28228
28808
  warnings,
28229
28809
  dedupe: true
28230
28810
  });
28811
+ setStoragePrivatePermissionEnforcement(config.storage.enforce_private_permissions);
28231
28812
  setSqlitePragmaConfig({
28232
28813
  cacheSizeMb: config.sqlite.cache_size_mb,
28233
28814
  mmapSizeMb: config.sqlite.mmap_size_mb
@@ -28267,6 +28848,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28267
28848
  language: cfg.language,
28268
28849
  autoSearch: auto,
28269
28850
  resolveForProject: resolveContextOptionsForProject,
28851
+ compactionOff,
28852
+ allowHomeProject: cfg.allow_home_project,
28270
28853
  maybeAutoEmbedSession: (sessionId, dir, identity) => {
28271
28854
  maybeAutoEmbedPiSession({
28272
28855
  db: database,
@@ -28288,7 +28871,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28288
28871
  hist.onStatusChange = (ctx) => {
28289
28872
  updateStatusLine(ctx, {
28290
28873
  db: database,
28291
- projectIdentity: resolveCurrentProject(ctx).projectIdentity ?? ""
28874
+ projectIdentity: resolveCurrentProject(ctx, cfg.allow_home_project).projectIdentity ?? ""
28292
28875
  });
28293
28876
  };
28294
28877
  }
@@ -28318,14 +28901,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28318
28901
  dedupe: true
28319
28902
  });
28320
28903
  const switchedConfig = switchedLoad.config;
28321
- const switchedIdentity = identityOverride ?? resolveProjectIdentityForSession(dir) ?? "";
28904
+ const switchedIdentity = identityOverride ?? resolveProjectIdentityForSession(dir, switchedConfig.allow_home_project) ?? "";
28322
28905
  const built = buildProjectDeps(dir, switchedIdentity, switchedConfig);
28323
28906
  projectDepsByDir.set(dir, built);
28324
28907
  return built;
28325
28908
  }
28326
28909
  function resolveCurrentProjectDeps(ctx) {
28327
- const currentProject = resolveCurrentProject(ctx);
28328
- return resolveProjectDepsForDir(currentProject.projectDir, currentProject.projectIdentity);
28910
+ return resolveProjectDepsForDir(ctx.cwd);
28329
28911
  }
28330
28912
  function resolveContextOptionsForProject(dir) {
28331
28913
  return resolveProjectDepsForDir(dir).contextOptions;
@@ -28341,11 +28923,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28341
28923
  memoryToolEnabled: true,
28342
28924
  protectedTags: config.protected_tags ?? 20,
28343
28925
  resolveProtectedTags: (ctx) => resolveCurrentProjectDeps(ctx).config.protected_tags ?? 20,
28926
+ resolveProjectIdentity: (ctx) => resolveCurrentProjectDeps(ctx).projectIdentity,
28344
28927
  dreamerEnabled: isDreamerRunnable(config),
28345
28928
  resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
28346
- todowriteEnabled
28929
+ todowriteEnabled,
28930
+ compactionOff
28347
28931
  });
28348
- 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)");
28349
28933
  pi.on("session_start", async (event, ctx) => {
28350
28934
  await handlePiCloneSessionStart(event, ctx, {
28351
28935
  db,
@@ -28389,7 +28973,6 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28389
28973
  return {
28390
28974
  db,
28391
28975
  projectIdentity: current.projectIdentity,
28392
- resolveProject: resolveCurrentProject,
28393
28976
  protectedTags: current.config.protected_tags,
28394
28977
  executeThresholdPercentage: current.config.execute_threshold_percentage,
28395
28978
  historyBudgetPercentage: current.config.history_budget_percentage,
@@ -28406,7 +28989,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28406
28989
  info("registered /ctx-status");
28407
28990
  registerStatusLine(pi, { db, projectIdentity });
28408
28991
  info("registered magic-context status line");
28409
- registerCtxFlushCommand(pi, { db });
28992
+ registerCtxFlushCommand(pi, { db, compactionOff });
28410
28993
  info("registered /ctx-flush");
28411
28994
  registerCtxRecompCommand(pi, {
28412
28995
  db,
@@ -28419,6 +29002,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28419
29002
  language: bootProjectDeps.config.language,
28420
29003
  memoryEnabled: bootProjectDeps.config.memory.enabled,
28421
29004
  autoPromote: bootProjectDeps.config.memory.auto_promote,
29005
+ compactionOff,
28422
29006
  resolveRuntimeDeps: (ctx) => {
28423
29007
  const current = resolveCurrentProjectDeps(ctx);
28424
29008
  return {
@@ -28431,7 +29015,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28431
29015
  historianThinkingLevel: current.historianConfig?.thinkingLevel,
28432
29016
  language: current.config.language,
28433
29017
  memoryEnabled: current.config.memory.enabled,
28434
- autoPromote: current.config.memory.auto_promote
29018
+ autoPromote: current.config.memory.auto_promote,
29019
+ compactionOff
28435
29020
  };
28436
29021
  }
28437
29022
  });
@@ -28447,6 +29032,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28447
29032
  language: bootProjectDeps.config.language,
28448
29033
  memoryEnabled: bootProjectDeps.config.memory.enabled,
28449
29034
  autoPromote: bootProjectDeps.config.memory.auto_promote,
29035
+ compactionOff,
28450
29036
  userMemoriesEnabled: userMemoryCollectionEnabled(bootProjectDeps.config.dreamer),
28451
29037
  executeThresholdPercentage: bootProjectDeps.config.execute_threshold_percentage,
28452
29038
  executeThresholdTokens: bootProjectDeps.config.execute_threshold_tokens,
@@ -28463,6 +29049,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28463
29049
  language: current.config.language,
28464
29050
  memoryEnabled: current.config.memory.enabled,
28465
29051
  autoPromote: current.config.memory.auto_promote,
29052
+ compactionOff,
28466
29053
  userMemoriesEnabled: userMemoryCollectionEnabled(current.config.dreamer),
28467
29054
  executeThresholdPercentage: current.config.execute_threshold_percentage,
28468
29055
  executeThresholdTokens: current.config.execute_threshold_tokens
@@ -28480,7 +29067,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28480
29067
  historianThinkingLevel: bootProjectDeps.historianConfig?.thinkingLevel,
28481
29068
  language: bootProjectDeps.config.language,
28482
29069
  memoryEnabled: bootProjectDeps.config.memory.enabled,
29070
+ allowHomeProject: bootProjectDeps.config.allow_home_project,
28483
29071
  autoPromote: bootProjectDeps.config.memory.auto_promote,
29072
+ compactionOff,
28484
29073
  userMemoriesEnabled: userMemoryCollectionEnabled(bootProjectDeps.config.dreamer),
28485
29074
  resolveRuntimeDeps: (ctx) => {
28486
29075
  const current = resolveCurrentProjectDeps(ctx);
@@ -28494,7 +29083,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28494
29083
  historianThinkingLevel: current.historianConfig?.thinkingLevel,
28495
29084
  language: current.config.language,
28496
29085
  memoryEnabled: current.config.memory.enabled,
29086
+ allowHomeProject: current.config.allow_home_project,
28497
29087
  autoPromote: current.config.memory.auto_promote,
29088
+ compactionOff,
28498
29089
  userMemoriesEnabled: userMemoryCollectionEnabled(current.config.dreamer)
28499
29090
  };
28500
29091
  }
@@ -28504,7 +29095,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28504
29095
  db,
28505
29096
  projectDir,
28506
29097
  projectIdentity,
28507
- resolveProject: resolveCurrentProject,
29098
+ resolveProject: (ctx) => {
29099
+ const current = resolveCurrentProjectDeps(ctx);
29100
+ return {
29101
+ projectDir: current.projectDir,
29102
+ projectIdentity: current.projectIdentity
29103
+ };
29104
+ },
28508
29105
  dreamerEnabled: bootProjectDeps.dreamerEnabled,
28509
29106
  resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
28510
29107
  onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity)
@@ -28516,7 +29113,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28516
29113
  projectIdentity,
28517
29114
  memoryEnabled: bootProjectDeps.config.memory.enabled,
28518
29115
  resolveMemoryEnabled: (ctx) => resolveCurrentProjectDeps(ctx).config.memory.enabled,
28519
- resolveProject: resolveCurrentProject
29116
+ resolveProject: (ctx) => {
29117
+ const current = resolveCurrentProjectDeps(ctx);
29118
+ return {
29119
+ projectDir: current.projectDir,
29120
+ projectIdentity: current.projectIdentity
29121
+ };
29122
+ }
28520
29123
  });
28521
29124
  info("registered /ctx-embed");
28522
29125
  const dreamerConfig = bootProjectDeps.dreamerConfig;
@@ -28605,11 +29208,11 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28605
29208
  try {
28606
29209
  const smForDrain = sm;
28607
29210
  const canDrain = typeof smForDrain.appendCompaction === "function" && typeof smForDrain.getBranch === "function";
28608
- if (canDrain && getPendingPiCompactionMarkerState(db, sessionId)) {
29211
+ if (!compactionOff && canDrain && getPendingPiCompactionMarkerState(db, sessionId)) {
28609
29212
  signalPiDeferredCompactionMarkerDrain(sessionId);
28610
29213
  }
28611
29214
  } catch {}
28612
- if (ctx.hasUI && effectiveProjectDeps.historianConfig?.model) {
29215
+ if (!compactionOff && ctx.hasUI && effectiveProjectDeps.historianConfig?.model) {
28613
29216
  maybeSendUpgradeReminder({
28614
29217
  client: null,
28615
29218
  db,
@@ -28637,7 +29240,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28637
29240
  memoryEnabled: effectiveConfig.memory.enabled,
28638
29241
  includeGuidance: true,
28639
29242
  protectedTags: effectiveConfig.protected_tags,
28640
- ctxReduceCallable: true,
29243
+ ctxReduceCallable: !compactionOff,
28641
29244
  dreamerEnabled: effectiveProjectDeps.dreamerEnabled,
28642
29245
  temporalAwarenessEnabled: effectiveConfig.temporal_awareness ?? false,
28643
29246
  cavemanTextCompressionEnabled: effectiveConfig.caveman_text_compression?.enabled === true,
@@ -28682,7 +29285,7 @@ ${block}` : event.systemPrompt;
28682
29285
  const lastAssistant = Array.isArray(msgs) ? [...msgs].reverse().find((m) => m?.role === "assistant") : undefined;
28683
29286
  if (lastAssistant?.stopReason === "stop") {
28684
29287
  const sessionId = ctx.sessionManager?.getSessionId?.();
28685
- if (sessionId && db)
29288
+ if (sessionId && db && !compactionOff)
28686
29289
  maybeDeliverChannel2Pi(pi, db, sessionId);
28687
29290
  }
28688
29291
  } catch (err) {
@@ -28707,7 +29310,7 @@ ${block}` : event.systemPrompt;
28707
29310
  toolCallId
28708
29311
  });
28709
29312
  if (Array.isArray(todos) && todos.length > 0 && todos.every((t) => t.status === "completed" || t.status === "cancelled")) {
28710
- if (sessionMeta && !sessionMeta.isSubagent) {
29313
+ if (!compactionOff && sessionMeta && !sessionMeta.isSubagent) {
28711
29314
  onNoteTrigger(db, sessionId, "todos_complete");
28712
29315
  }
28713
29316
  }
@@ -28723,7 +29326,7 @@ ${block}` : event.systemPrompt;
28723
29326
  const sessionId = ctx.sessionManager.getSessionId();
28724
29327
  if (typeof sessionId !== "string" || sessionId.length === 0)
28725
29328
  return;
28726
- if (event.toolName === "ctx_reduce") {
29329
+ if (!compactionOff && event.toolName === "ctx_reduce") {
28727
29330
  markPiChannel1Reduced(sessionId, db);
28728
29331
  }
28729
29332
  } catch (err) {
@@ -28735,6 +29338,8 @@ ${block}` : event.systemPrompt;
28735
29338
  const sessionId = ctx.sessionManager.getSessionId();
28736
29339
  if (typeof sessionId !== "string" || sessionId.length === 0)
28737
29340
  return;
29341
+ if (compactionOff)
29342
+ return;
28738
29343
  if (db)
28739
29344
  maybeDeliverChannel2Pi(pi, db, sessionId, "steer");
28740
29345
  const block = maybeChannel1ReminderForToolResult({
@@ -28750,20 +29355,11 @@ ${block}` : event.systemPrompt;
28750
29355
  log(`tool_result hook failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
28751
29356
  }
28752
29357
  });
28753
- pi.on("session_before_compact", async (_event, ctx) => {
28754
- try {
28755
- const sessionId = ctx.sessionManager?.getSessionId?.();
28756
- if (typeof sessionId === "string" && sessionId.length > 0) {
28757
- clearPiM0Cache(db, sessionId, "session_before_compact");
28758
- }
28759
- } catch {}
28760
- info("session_before_compact: cancelling — magic-context owns compaction");
28761
- return { cancel: true };
28762
- });
29358
+ pi.on("session_before_compact", async (_event, ctx) => handlePiSessionBeforeCompact({ db, compactionOff, ctx }));
28763
29359
  pi.on("message_end", async (event, ctx) => {
28764
29360
  try {
28765
29361
  const msg = event.message;
28766
- if (msg !== null && typeof msg === "object") {
29362
+ if (!compactionOff && msg !== null && typeof msg === "object") {
28767
29363
  stripTagPrefixFromAssistantMessage(msg);
28768
29364
  }
28769
29365
  } catch (err) {
@@ -28795,6 +29391,7 @@ ${block}` : event.systemPrompt;
28795
29391
  sessionId,
28796
29392
  message: event.message,
28797
29393
  piContextWindow,
29394
+ piModel: ctx.model,
28798
29395
  piTokens: piUsage && typeof piUsage.tokens === "number" ? piUsage.tokens : undefined,
28799
29396
  notifyIssue: async (message) => {
28800
29397
  const uiNotify = ctx.ui?.notify;
@@ -28824,6 +29421,8 @@ ${block}` : event.systemPrompt;
28824
29421
  warn("message_end: persist session_meta usage failed:", err);
28825
29422
  }
28826
29423
  try {
29424
+ if (compactionOff)
29425
+ return;
28827
29426
  const sm = ctx.sessionManager;
28828
29427
  const sessionId = sm?.getSessionId?.();
28829
29428
  if (typeof sessionId !== "string" || sessionId.length === 0)
@@ -28841,8 +29440,8 @@ ${block}` : event.systemPrompt;
28841
29440
  if (!detection.isOverflow)
28842
29441
  return;
28843
29442
  const modelKey = typeof msg.provider === "string" && typeof msg.model === "string" && msg.provider.length > 0 && msg.model.length > 0 ? `${msg.provider}/${msg.model}` : undefined;
28844
- recordOverflowDetected(db, sessionId, detection.reportedLimit, modelKey);
28845
- 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 ?? "?"}`);
28846
29445
  } catch (err) {
28847
29446
  warn("message_end: overflow detection failed:", err);
28848
29447
  }
@@ -28879,6 +29478,7 @@ ${block}` : event.systemPrompt;
28879
29478
  clearContextHandlerSession(sessionId);
28880
29479
  }
28881
29480
  } catch {}
29481
+ clearPiMagicContextActive();
28882
29482
  });
28883
29483
  pi.on("session_before_switch", (_event, ctx) => {
28884
29484
  try {
@@ -28907,6 +29507,7 @@ export {
28907
29507
  resolveDreamerFromConfig,
28908
29508
  persistPiPressureFromMessageEnd,
28909
29509
  persistPiMessageEndModelMeta,
29510
+ handlePiSessionBeforeCompact,
28910
29511
  src_default as default,
28911
29512
  capturePiTodowriteMessageIfCompatible,
28912
29513
  capturePiTodowriteArgsIfCompatible,