@cortexkit/pi-magic-context 0.33.1 → 0.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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,
@@ -191,6 +200,7 @@ import {
191
200
  hasMeaningfulUserText,
192
201
  hasMemoryClassifiedAtColumn,
193
202
  hasMemoryShareableColumn,
203
+ hasMuralCueColumns,
194
204
  hasPiFallbackMessageTags,
195
205
  hasPiFallbackToolOwnerTags,
196
206
  incrementHistorianFailure,
@@ -203,11 +213,13 @@ import {
203
213
  insertUserMemory,
204
214
  insertUserMemoryCandidates,
205
215
  invalidateMemory,
216
+ isCompactionEnabled,
206
217
  isCompartmentLeaseHeld,
207
218
  isDreamerRunnable,
208
219
  isEditTool,
209
220
  isMessageIndexReconciledThrough,
210
221
  isMessageIndexSourceCurrent,
222
+ isMidTurn,
211
223
  isMidTurnPi,
212
224
  isRecord,
213
225
  isSaneLimit,
@@ -282,7 +294,9 @@ import {
282
294
  reserveProtectedTailDrainTokens,
283
295
  resetLastNudgeCycle,
284
296
  resetLastNudgeCycleIfTailShrank,
297
+ resolveCompactionModeRecord,
285
298
  resolveGitTopLevel,
299
+ resolveLimit,
286
300
  resolveModelRefForPi,
287
301
  resolveMuralWire,
288
302
  resolvePiStableId,
@@ -298,6 +312,7 @@ import {
298
312
  saveSourceContent,
299
313
  scheduleAfterBootQuiet,
300
314
  setChannel2NudgeState,
315
+ setCompactionModeRecord,
301
316
  setDeferredExecutePendingIfAbsent,
302
317
  setEmergencyDropSample,
303
318
  setLastNudgeLevel,
@@ -312,6 +327,7 @@ import {
312
327
  setRecompPartialRange,
313
328
  setSessionWorkMetrics,
314
329
  setSqlitePragmaConfig,
330
+ setStoragePrivatePermissionEnforcement,
315
331
  setTodoSnapshot,
316
332
  sourceNameForMemory,
317
333
  stableStringify,
@@ -346,7 +362,10 @@ import {
346
362
  withMigrationLanguageDirective,
347
363
  withRawMessageProvider,
348
364
  withRawSessionMessageCache
349
- } from "./index-e3kftg52.js";
365
+ } from "./index-ekzg3w5y.js";
366
+ import {
367
+ pushNotification
368
+ } from "./index-hw34xmzk.js";
350
369
  import {
351
370
  ensureCortexKitArtifactGitignore,
352
371
  getHarness,
@@ -425,6 +444,16 @@ var CANONICAL_DREAM_TASKS = [
425
444
  "promote-primers",
426
445
  "refresh-primers"
427
446
  ];
447
+ function formatDreamTaskBacklogs(backlogs, tasks = CANONICAL_DREAM_TASKS) {
448
+ return tasks.filter((task) => backlogs[task] !== undefined).map((task) => {
449
+ const backlog = backlogs[task];
450
+ return `- ${task}: ${backlog?.pending ?? 0} pending / ${backlog?.total ?? 0} total`;
451
+ }).join(`
452
+ `);
453
+ }
454
+ function processedDreamTaskItems(startPending, endPending) {
455
+ return Math.max(0, startPending - endPending);
456
+ }
428
457
  var MEMORY_DOMAIN_TASKS = [
429
458
  "map-memories",
430
459
  "verify",
@@ -528,14 +557,41 @@ function isMagicContextHiddenAgentName(agent) {
528
557
  return true;
529
558
  return false;
530
559
  }
560
+ var MAX_FORMATTED_BLOCKING_PROCESSES = 8;
561
+ function formatFailClosedBlockingProcesses(processes) {
562
+ const uniqueProcesses = new Map;
563
+ for (const process2 of processes) {
564
+ if (!process2.harness.trim() || !Number.isInteger(process2.pid) || process2.pid <= 0)
565
+ continue;
566
+ uniqueProcesses.set(`${process2.harness}\x00${process2.pid}`, {
567
+ harness: process2.harness.trim(),
568
+ pid: process2.pid
569
+ });
570
+ }
571
+ const entries = [...uniqueProcesses.values()];
572
+ const visible = entries.slice(0, MAX_FORMATTED_BLOCKING_PROCESSES);
573
+ const rendered = visible.map(({ harness, pid }) => `${harness} (PID ${pid})`);
574
+ const omitted = entries.length - visible.length;
575
+ if (omitted > 0)
576
+ rendered.push(`${omitted} more blocking process(es)`);
577
+ if (rendered.length === 0)
578
+ return "a live harness process";
579
+ if (rendered.length === 1)
580
+ return rendered[0];
581
+ const last = rendered.pop();
582
+ return `${rendered.join(", ")}, and ${last}`;
583
+ }
531
584
  function formatFailClosedBlockingMessage(reason) {
585
+ if (reason.kind === "migration_guard") {
586
+ return [
587
+ `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.`,
588
+ "Restart the blocking process (it will pick up the new build and migrate on start), or shut it down and retry.",
589
+ `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
590
+ ].join(" ");
591
+ }
532
592
  if (reason.kind === "schema_fence") {
533
593
  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.`,
594
+ `Magic Context cannot operate: this Magic Context build is older than the database; upgrade/restart this harness (upstream migration lane v${reason.persistedVersion}, build supports through v${reason.supportedVersion}).`,
539
595
  `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
540
596
  ].join(" ");
541
597
  }
@@ -596,7 +652,7 @@ function createFailClosedController(options) {
596
652
  const shouldReprobe = typeof input.tryReopen === "function" && (blockedPassCount === 1 || blockedPassCount % reprobeEveryN === 0);
597
653
  if (shouldReprobe) {
598
654
  try {
599
- const healed = await input.tryReopen();
655
+ const healed = await input.tryReopen?.();
600
656
  if (healed) {
601
657
  reason = null;
602
658
  blockedPassCount = 0;
@@ -779,6 +835,7 @@ var OVERFLOW_PATTERNS = [
779
835
  /maximum prompt length is \d+/i,
780
836
  /reduce the length of the messages/i,
781
837
  /maximum context length is \d+ tokens/i,
838
+ /maximum model length is \d+/i,
782
839
  /exceeds the limit of \d+/i,
783
840
  /exceeds the available context size/i,
784
841
  /greater than the context length/i,
@@ -794,15 +851,25 @@ var OVERFLOW_PATTERNS = [
794
851
  /context size has been exceeded/i
795
852
  ];
796
853
  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
854
+ { pattern: /maximum prompt length is (\d+)/i, provenance: "prompt_only" },
855
+ {
856
+ pattern: /maximum context length is (\d+) tokens?/i,
857
+ provenance: "combined"
858
+ },
859
+ { pattern: /maximum model length is (\d+)/i, provenance: "combined" },
860
+ { pattern: /context length is only (\d+) tokens?/i, provenance: "combined" },
861
+ { pattern: /exceeds the limit of (\d+)/i, provenance: "unknown" },
862
+ {
863
+ pattern: /too large for model with (\d+) maximum context length/i,
864
+ provenance: "combined"
865
+ },
866
+ { pattern: /context size.*(\d+) tokens?/i, provenance: "combined" },
867
+ { pattern: /exceeds? the context length of (\d+)/i, provenance: "combined" },
868
+ {
869
+ pattern: />\s*(\d+)\s*(?:tokens?\s*)?(?:maximum|max|limit)\b/i,
870
+ provenance: "prompt_only"
871
+ },
872
+ { pattern: /max(?:imum)?.*context.*?(\d+)/i, provenance: "unknown" }
806
873
  ];
807
874
  var MIN_PLAUSIBLE_LIMIT = 1024;
808
875
  var MAX_PLAUSIBLE_LIMIT = 1e7;
@@ -853,14 +920,15 @@ function detectOverflow(error) {
853
920
  const reportedLimit = parseReportedLimit(message);
854
921
  return {
855
922
  isOverflow: true,
856
- reportedLimit,
923
+ reportedLimit: reportedLimit?.value,
924
+ reportedLimitProvenance: reportedLimit?.provenance,
857
925
  matchedPattern: matched?.source
858
926
  };
859
927
  }
860
928
  function parseReportedLimit(message) {
861
929
  if (!message)
862
930
  return;
863
- for (const pattern of LIMIT_EXTRACTION_PATTERNS) {
931
+ for (const { pattern, provenance } of LIMIT_EXTRACTION_PATTERNS) {
864
932
  const match = message.match(pattern);
865
933
  if (!match)
866
934
  continue;
@@ -872,7 +940,7 @@ function parseReportedLimit(message) {
872
940
  continue;
873
941
  if (value < MIN_PLAUSIBLE_LIMIT || value > MAX_PLAUSIBLE_LIMIT)
874
942
  continue;
875
- return value;
943
+ return { value, provenance };
876
944
  }
877
945
  return;
878
946
  }
@@ -1354,6 +1422,15 @@ async function runDeferredV22Backfill(db, options = {}) {
1354
1422
  };
1355
1423
  }
1356
1424
 
1425
+ // ../plugin/src/hooks/magic-context/ctx-reduce-availability.ts
1426
+ var ctxReduceRegisteredGlobally = true;
1427
+ function setCtxReduceRegisteredGlobally(registered) {
1428
+ ctxReduceRegisteredGlobally = registered;
1429
+ }
1430
+ var availabilityBySession = new BoundedSessionMap(1000);
1431
+ var permissionDeniedBySession = new BoundedSessionMap(2000);
1432
+ var ctxReducePermissionDenyLogged = new BoundedSessionMap(1000);
1433
+
1357
1434
  // ../plugin/src/hooks/magic-context/derive-budgets.ts
1358
1435
  var TRIGGER_BUDGET_PERCENTAGE = 0.05;
1359
1436
  var TRIGGER_BUDGET_MIN = 5000;
@@ -1397,7 +1474,6 @@ function resolveHistorianContextLimit(historianModelOverride) {
1397
1474
 
1398
1475
  // ../plugin/src/hooks/magic-context/event-resolvers.ts
1399
1476
  var DEFAULT_CONTEXT_LIMIT = 128000;
1400
- var MAX_EXECUTE_THRESHOLD = 80;
1401
1477
  function resolveCacheTtl(cacheTtl, modelKey) {
1402
1478
  if (typeof cacheTtl === "string") {
1403
1479
  return cacheTtl;
@@ -1442,7 +1518,7 @@ function resolveExecuteThresholdDetail(config, modelKey, fallback, options) {
1442
1518
  const dedupeKey = `${options.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${tokenMatch.value}|${cap}`;
1443
1519
  if (!clampWarnSeen.has(dedupeKey)) {
1444
1520
  clampWarnSeen.add(dedupeKey);
1445
- const msg = `execute_threshold_tokens clamped: ${tokenMatch.value} → ${effectiveTokens} (80% of ${contextLimit}) for ${modelKey ?? "default"}`;
1521
+ const msg = `execute_threshold_tokens clamped: ${tokenMatch.value} → ${effectiveTokens} (${MAX_EXECUTE_THRESHOLD}% of ${contextLimit}) for ${modelKey ?? "default"}`;
1446
1522
  if (options.sessionId) {
1447
1523
  sessionLog(options.sessionId, `WARN: ${msg}`);
1448
1524
  } else {
@@ -1498,7 +1574,7 @@ function resolveExecuteThresholdDetail(config, modelKey, fallback, options) {
1498
1574
  const dedupeKey = `pct|${options?.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${resolved}`;
1499
1575
  if (!clampWarnSeen.has(dedupeKey)) {
1500
1576
  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)`;
1577
+ 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
1578
  if (options?.sessionId) {
1503
1579
  sessionLog(options.sessionId, `WARN: ${msg}`);
1504
1580
  } else {
@@ -1990,6 +2066,57 @@ import {
1990
2066
  import { createInterface } from "node:readline";
1991
2067
  import { fileURLToPath } from "node:url";
1992
2068
 
2069
+ // ../plugin/src/features/magic-context/schema-fence-probe.ts
2070
+ var STALE_CHILD_SPAWN_FAILURE = "stale_schema_fence";
2071
+ var STALE_CHILD_SPAWN_LATCH_THRESHOLD = 2;
2072
+ var state = {
2073
+ consecutiveFailures: 0,
2074
+ totalFailures: 0,
2075
+ latched: false,
2076
+ noticeEmitted: false,
2077
+ failure: null
2078
+ };
2079
+ function recordStaleFence(persistedVersion, supportedVersion) {
2080
+ state.consecutiveFailures += 1;
2081
+ state.totalFailures += 1;
2082
+ const latched = state.consecutiveFailures >= STALE_CHILD_SPAWN_LATCH_THRESHOLD;
2083
+ state.latched ||= latched;
2084
+ const failure = {
2085
+ failureClass: STALE_CHILD_SPAWN_FAILURE,
2086
+ persistedVersion,
2087
+ supportedVersion,
2088
+ consecutiveFailures: state.consecutiveFailures,
2089
+ totalFailures: state.totalFailures,
2090
+ latched: state.latched
2091
+ };
2092
+ state.failure = failure;
2093
+ const shouldSurface = state.latched && !state.noticeEmitted;
2094
+ if (shouldSurface)
2095
+ state.noticeEmitted = true;
2096
+ return { allowSpawn: false, failure, shouldSurface };
2097
+ }
2098
+ function probeChildSpawnFence(db) {
2099
+ if (!db) {
2100
+ const knownRejection = getSchemaFenceRejection();
2101
+ if (knownRejection) {
2102
+ return recordStaleFence(knownRejection.persistedVersion, knownRejection.supportedVersion);
2103
+ }
2104
+ return { allowSpawn: true };
2105
+ }
2106
+ try {
2107
+ const persistedVersion = getPersistedSchemaVersion(db);
2108
+ if (persistedVersion > LATEST_SUPPORTED_VERSION) {
2109
+ return recordStaleFence(persistedVersion, LATEST_SUPPORTED_VERSION);
2110
+ }
2111
+ } catch {
2112
+ return { allowSpawn: true };
2113
+ }
2114
+ state.consecutiveFailures = 0;
2115
+ state.latched = false;
2116
+ state.noticeEmitted = false;
2117
+ return { allowSpawn: true };
2118
+ }
2119
+
1993
2120
  // ../plugin/src/features/magic-context/subagent-token-capture.ts
1994
2121
  function asNumber(value) {
1995
2122
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
@@ -2338,6 +2465,10 @@ class PiSubagentRunner {
2338
2465
  if (ZERO_TOOL_PROMPT_REQUIRED_AGENTS.has(options.agent) && options.systemPrompt.trim().length === 0) {
2339
2466
  return failBeforeSpawn("invalid_prompt", `zero-tool Pi subagent "${options.agent}" requires a non-empty system prompt`, true);
2340
2467
  }
2468
+ const fence = probeChildSpawnFence(openDatabase());
2469
+ if (!fence.allowSpawn) {
2470
+ 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.`);
2471
+ }
2341
2472
  const promptBytes = Buffer.byteLength(options.userMessage, "utf8");
2342
2473
  const deliverViaStdin = promptBytes > PROMPT_ARGV_MAX_BYTES || this.platform === "win32";
2343
2474
  let systemPromptTempDir;
@@ -2909,7 +3040,7 @@ function registerCtxAugCommand(pi, config) {
2909
3040
  sessionLog(sessionLabel, "/ctx-aug: spawning sidekick", {
2910
3041
  model: currentConfig.model
2911
3042
  });
2912
- const projectIdentity = resolveProjectIdentityForSession(ctx.cwd);
3043
+ const projectIdentity = resolveProjectIdentityForSession(ctx.cwd, currentConfig.allowHomeProject);
2913
3044
  if (!projectIdentity) {
2914
3045
  sessionLog(sessionLabel, "Error: Could not resolve project identity for sidekick.");
2915
3046
  return;
@@ -2952,6 +3083,394 @@ ${sidekickText}
2952
3083
  });
2953
3084
  }
2954
3085
 
3086
+ // ../plugin/src/features/magic-context/smart-notes/types.ts
3087
+ var SMART_NOTE_CHECK_POLICY_VERSION = 1;
3088
+ var SMART_NOTE_CHECK_FLOOR_MS = 5 * 60 * 1000;
3089
+ var SMART_NOTE_CHECK_CEILING_MS = 24 * 60 * 60 * 1000;
3090
+ var SMART_NOTE_CHECK_DEFAULT_INTERVAL_MS = 60 * 60 * 1000;
3091
+ var SMART_NOTE_CHECK_MAX_STALENESS_MS = 7 * 24 * 60 * 60 * 1000;
3092
+ var SMART_NOTE_CHECK_LIVENESS_RECHECK_MS = 24 * 60 * 60 * 1000;
3093
+
3094
+ class SmartNoteNetworkError extends Error {
3095
+ isSmartNoteNetworkError = true;
3096
+ terminal;
3097
+ constructor(message, options = {}) {
3098
+ super(message);
3099
+ this.name = "SmartNoteNetworkError";
3100
+ this.terminal = options.terminal ?? false;
3101
+ }
3102
+ }
3103
+
3104
+ class SmartNoteSecurityError extends Error {
3105
+ isSmartNoteSecurityError = true;
3106
+ constructor(message) {
3107
+ super(message);
3108
+ this.name = "SmartNoteSecurityError";
3109
+ }
3110
+ }
3111
+ function isSmartNoteNetworkError(error) {
3112
+ return error instanceof SmartNoteNetworkError || error instanceof Error && (error.name === "SmartNoteNetworkError" || error.message.includes("SmartNoteNetworkError") || error.message.includes("SMART_NOTE_NETWORK"));
3113
+ }
3114
+ function isTerminalSmartNoteNetworkError(error) {
3115
+ return error instanceof SmartNoteNetworkError && error.terminal;
3116
+ }
3117
+ function parseSmartNoteManifest(json) {
3118
+ if (!json)
3119
+ return { capabilities: [] };
3120
+ try {
3121
+ const parsed = JSON.parse(json);
3122
+ const capabilities = Array.isArray(parsed.capabilities) ? parsed.capabilities.filter((c) => ["readFile", "gitHeadSha", "gitTag", "gitLog", "httpGet"].includes(String(c))) : [];
3123
+ return {
3124
+ capabilities,
3125
+ readFiles: stringArray(parsed.readFiles),
3126
+ hosts: stringArray(parsed.hosts),
3127
+ urls: stringArray(parsed.urls),
3128
+ signals: stringArray(parsed.signals),
3129
+ summary: typeof parsed.summary === "string" ? parsed.summary : undefined
3130
+ };
3131
+ } catch {
3132
+ return { capabilities: [] };
3133
+ }
3134
+ }
3135
+ function stringArray(value) {
3136
+ if (!Array.isArray(value))
3137
+ return;
3138
+ const arr = value.filter((item) => typeof item === "string");
3139
+ return arr.length > 0 ? arr : undefined;
3140
+ }
3141
+
3142
+ // ../plugin/src/features/magic-context/smart-notes/storage.ts
3143
+ function toSmartNote(note) {
3144
+ return {
3145
+ ...note,
3146
+ checkStatus: note.checkStatus ?? "uncompiled",
3147
+ checkFailureCount: note.checkFailureCount ?? 0,
3148
+ checkNetworkFailureCount: note.checkNetworkFailureCount ?? 0,
3149
+ policyVersion: note.policyVersion ?? 0
3150
+ };
3151
+ }
3152
+ function commitSmartNoteState(db, args) {
3153
+ db.exec("BEGIN IMMEDIATE");
3154
+ let leaseLost = false;
3155
+ let committed = false;
3156
+ try {
3157
+ if (args.leaseHeld && !args.leaseHeld()) {
3158
+ leaseLost = true;
3159
+ } else if (claimExpectedState(db, args.expected)) {
3160
+ args.write();
3161
+ committed = true;
3162
+ }
3163
+ db.exec("COMMIT");
3164
+ } catch (error) {
3165
+ try {
3166
+ db.exec("ROLLBACK");
3167
+ } catch {}
3168
+ throw error;
3169
+ }
3170
+ if (leaseLost) {
3171
+ throw new Error(`Dream lease lost during smart-note ${args.phase} commit`);
3172
+ }
3173
+ if (!committed) {
3174
+ log(`[debug] smart note #${args.expected.noteId}: discarded stale ${args.phase} result`);
3175
+ }
3176
+ return committed;
3177
+ }
3178
+ function claimExpectedState(db, expected) {
3179
+ if (expected.kind === "compiled-check") {
3180
+ return db.prepare(`UPDATE notes SET id = id
3181
+ WHERE id = ? AND type = 'smart' AND status = 'pending'
3182
+ AND check_status = 'compiled' AND compiled_check = ?
3183
+ AND check_hash IS ? AND check_compiled_at IS ?`).run(expected.noteId, expected.compiledCheck, expected.checkHash, expected.checkCompiledAt).changes > 0;
3184
+ }
3185
+ const statusClause = expected.checkStatus ? " AND check_status = ?" : "";
3186
+ const params = [
3187
+ expected.noteId,
3188
+ expected.content,
3189
+ expected.surfaceCondition,
3190
+ expected.updatedAt
3191
+ ];
3192
+ if (expected.checkStatus)
3193
+ params.push(expected.checkStatus);
3194
+ return db.prepare(`UPDATE notes SET id = id
3195
+ WHERE id = ? AND type = 'smart' AND status = 'pending'
3196
+ AND content = ? AND surface_condition IS ? AND updated_at = ?${statusClause}`).run(...params).changes > 0;
3197
+ }
3198
+ function getDueCompiledSmartNoteChecks(db, projectPath, now, limit) {
3199
+ return getPendingSmartNotes(db, projectPath).map(toSmartNote).filter((note) => note.checkStatus === "compiled" && note.compiledCheck !== null && note.policyVersion === SMART_NOTE_CHECK_POLICY_VERSION && (note.checkQuarantinedUntil === null || note.checkQuarantinedUntil <= now) && (note.checkNextDueAt === null || note.checkNextDueAt <= now)).sort((a, b) => (a.checkNextDueAt ?? 0) - (b.checkNextDueAt ?? 0) || a.id - b.id).slice(0, Math.max(1, limit));
3200
+ }
3201
+ function getSmartNotesNeedingCompilation(db, projectPath, now, limit) {
3202
+ return getPendingSmartNotes(db, projectPath).map(toSmartNote).filter((note) => (note.checkNextDueAt === null || note.checkNextDueAt <= now) && (note.checkStatus === "uncompiled" || note.checkStatus === "failing" || note.compiledCheck === null || note.policyVersion !== SMART_NOTE_CHECK_POLICY_VERSION)).sort((a, b) => a.createdAt - b.createdAt || a.id - b.id).slice(0, Math.max(1, limit));
3203
+ }
3204
+ function getStaleCompiledSmartNotes(db, projectPath, now, limit) {
3205
+ const staleBefore = now - SMART_NOTE_CHECK_MAX_STALENESS_MS;
3206
+ const livenessBefore = now - SMART_NOTE_CHECK_LIVENESS_RECHECK_MS;
3207
+ return getPendingSmartNotes(db, projectPath).map(toSmartNote).filter((note) => note.checkStatus === "compiled" && note.compiledCheck !== null && note.policyVersion === SMART_NOTE_CHECK_POLICY_VERSION && note.checkFalseSinceAt !== null && note.checkFalseSinceAt <= staleBefore && (note.checkLastLivenessAt === null || note.checkLastLivenessAt <= livenessBefore)).sort((a, b) => (a.checkFalseSinceAt ?? 0) - (b.checkFalseSinceAt ?? 0) || a.id - b.id).slice(0, Math.max(1, limit));
3208
+ }
3209
+ function storeCompiledSmartNoteCheck(db, args) {
3210
+ db.prepare(`UPDATE notes
3211
+ SET compiled_check = ?,
3212
+ manifest_json = ?,
3213
+ check_hash = ?,
3214
+ check_cron = ?,
3215
+ check_version = 1,
3216
+ check_status = 'compiled',
3217
+ check_failure_count = 0,
3218
+ check_network_failure_count = 0,
3219
+ check_quarantined_until = NULL,
3220
+ check_next_due_at = ?,
3221
+ check_compiled_at = ?,
3222
+ check_false_since_at = COALESCE(check_false_since_at, ?),
3223
+ check_last_liveness_at = NULL,
3224
+ policy_version = ?,
3225
+ updated_at = ?
3226
+ WHERE id = ? AND type = 'smart'`).run(args.compiledCheck, JSON.stringify(args.manifest), args.checkHash, args.checkCron, args.nextDueAt, args.now, args.now, SMART_NOTE_CHECK_POLICY_VERSION, args.now, args.noteId);
3227
+ }
3228
+ function markCompiledCheckFalse(db, noteId, nextDueAt, now) {
3229
+ db.prepare(`UPDATE notes
3230
+ SET last_checked_at = ?,
3231
+ updated_at = ?,
3232
+ check_next_due_at = ?,
3233
+ check_failure_count = 0,
3234
+ check_network_failure_count = 0,
3235
+ check_false_since_at = COALESCE(check_false_since_at, ?)
3236
+ WHERE id = ? AND type = 'smart'`).run(now, now, nextDueAt, now, noteId);
3237
+ }
3238
+ function markCompiledCheckLogicFailure(db, noteId, now, maxFailures) {
3239
+ const failureCount = readFailureCount(db, noteId, "check_failure_count") + 1;
3240
+ const status = failureCount >= maxFailures ? "failing" : "compiled";
3241
+ db.prepare(`UPDATE notes
3242
+ SET check_failure_count = ?,
3243
+ check_status = ?,
3244
+ check_next_due_at = ?,
3245
+ updated_at = ?
3246
+ WHERE id = ? AND type = 'smart'`).run(failureCount, status, now + backoffMs(failureCount), now, noteId);
3247
+ }
3248
+ function markCompiledCheckNetworkFailure(db, noteId, now, maxFailures) {
3249
+ const failureCount = readFailureCount(db, noteId, "check_network_failure_count") + 1;
3250
+ const quarantinedUntil = now + backoffMs(failureCount);
3251
+ const status = failureCount >= maxFailures ? "failing" : "compiled";
3252
+ db.prepare(`UPDATE notes
3253
+ SET check_network_failure_count = ?,
3254
+ check_status = ?,
3255
+ check_next_due_at = ?,
3256
+ check_quarantined_until = ?,
3257
+ updated_at = ?
3258
+ WHERE id = ? AND type = 'smart'`).run(failureCount, status, quarantinedUntil, quarantinedUntil, now, noteId);
3259
+ }
3260
+ function markSmartNoteLivenessChecked(db, noteId, now) {
3261
+ db.prepare(`UPDATE notes
3262
+ SET check_last_liveness_at = ?, updated_at = ?
3263
+ WHERE id = ? AND type = 'smart'`).run(now, now, noteId);
3264
+ }
3265
+ function markSmartNoteCheckStatus(db, noteId, status, now) {
3266
+ db.prepare(`UPDATE notes SET check_status = ?, updated_at = ? WHERE id = ? AND type = 'smart'`).run(status, now, noteId);
3267
+ }
3268
+ function markSmartNoteCompilationFailure(db, noteId, now, maxFailures) {
3269
+ const failureCount = readFailureCount(db, noteId, "check_failure_count") + 1;
3270
+ const status = failureCount >= maxFailures ? "fallback" : "uncompiled";
3271
+ db.prepare(`UPDATE notes
3272
+ SET check_failure_count = ?,
3273
+ check_status = ?,
3274
+ check_next_due_at = ?,
3275
+ updated_at = ?
3276
+ WHERE id = ? AND type = 'smart'`).run(failureCount, status, now + backoffMs(failureCount), now, noteId);
3277
+ }
3278
+ function readFailureCount(db, noteId, column) {
3279
+ if (column !== "check_failure_count" && column !== "check_network_failure_count")
3280
+ return 0;
3281
+ const row = db.prepare(`SELECT ${column} AS count FROM notes WHERE id = ?`).get(noteId);
3282
+ return row?.count ?? 0;
3283
+ }
3284
+ function backoffMs(failureCount) {
3285
+ const minutes = Math.min(24 * 60, 5 * 2 ** Math.max(0, failureCount - 1));
3286
+ return minutes * 60 * 1000;
3287
+ }
3288
+
3289
+ // ../plugin/src/features/magic-context/dreamer/task-gates.ts
3290
+ function countActiveMemories(db, projectPath) {
3291
+ const row = db.prepare("SELECT COUNT(*) AS cnt FROM memories WHERE project_path = ? AND status IN ('active','permanent')").get(projectPath);
3292
+ return row?.cnt ?? 0;
3293
+ }
3294
+ function countUnmappedActiveMemories(db, projectPath) {
3295
+ const row = db.prepare(`SELECT COUNT(*) AS cnt
3296
+ FROM memories m
3297
+ WHERE m.project_path = ?
3298
+ AND m.status IN ('active','permanent')
3299
+ AND NOT EXISTS (
3300
+ SELECT 1 FROM memory_verifications v WHERE v.memory_id = m.id
3301
+ )`).get(projectPath);
3302
+ return row?.cnt ?? 0;
3303
+ }
3304
+ function countCompartmentsSince(db, projectPath, since) {
3305
+ const row = db.prepare(`SELECT COUNT(*) AS cnt
3306
+ FROM compartments c
3307
+ JOIN session_projects sp ON sp.session_id = c.session_id
3308
+ WHERE sp.project_path = ? AND c.created_at > ?`).get(projectPath, since);
3309
+ return row?.cnt ?? 0;
3310
+ }
3311
+ function countProjectSessionsSince(db, projectPath, since) {
3312
+ const row = since === null ? db.prepare("SELECT COUNT(*) AS cnt FROM session_projects WHERE project_path = ?").get(projectPath) : db.prepare("SELECT COUNT(*) AS cnt FROM session_projects WHERE project_path = ? AND updated_at > ?").get(projectPath, since);
3313
+ return row?.cnt ?? 0;
3314
+ }
3315
+ function countMappedMemories(db, projectPath) {
3316
+ const row = db.prepare(`SELECT COUNT(DISTINCT m.id) AS cnt
3317
+ FROM memories m
3318
+ JOIN memory_verifications v ON v.memory_id = m.id
3319
+ WHERE m.project_path = ?
3320
+ AND m.status IN ('active','permanent')
3321
+ AND v.file_path <> ''`).get(projectPath);
3322
+ return row?.cnt ?? 0;
3323
+ }
3324
+ function countUnverifiedMappedMemories(db, projectPath) {
3325
+ const row = db.prepare(`SELECT COUNT(DISTINCT m.id) AS cnt
3326
+ FROM memories m
3327
+ JOIN memory_verifications v ON v.memory_id = m.id
3328
+ WHERE m.project_path = ?
3329
+ AND m.status IN ('active','permanent')
3330
+ AND v.file_path <> ''
3331
+ AND v.verified_at = 0`).get(projectPath);
3332
+ return row?.cnt ?? 0;
3333
+ }
3334
+ function countCueCandidates(db, projectPath) {
3335
+ if (!hasMuralCueColumns(db))
3336
+ return countActiveMemories(db, projectPath);
3337
+ const row = db.prepare(`SELECT COUNT(*) AS cnt
3338
+ FROM memories
3339
+ WHERE project_path = ?
3340
+ AND status IN ('active','permanent')
3341
+ AND (mural_cue IS NULL OR mural_cue_hash IS NULL OR updated_at > mural_cue_at)`).get(projectPath);
3342
+ return row?.cnt ?? 0;
3343
+ }
3344
+ function countStalePrimers(db, projectPath) {
3345
+ const row = db.prepare(`SELECT COUNT(*) AS cnt
3346
+ FROM primers
3347
+ WHERE project_path = ?
3348
+ AND status = 'active'
3349
+ AND (answer IS NULL OR TRIM(answer) = '' OR answer_refreshed_at IS NULL
3350
+ OR last_observed_at > answer_refreshed_at)`).get(projectPath);
3351
+ return row?.cnt ?? 0;
3352
+ }
3353
+ function countUnclassifiedActiveMemories(db, projectPath) {
3354
+ if (!hasMemoryClassifiedAtColumn(db))
3355
+ return countActiveMemories(db, projectPath);
3356
+ const row = db.prepare(`SELECT COUNT(*) AS cnt
3357
+ FROM memories
3358
+ WHERE project_path = ?
3359
+ AND status IN ('active','permanent')
3360
+ AND classified_at IS NULL`).get(projectPath);
3361
+ return row?.cnt ?? 0;
3362
+ }
3363
+ function countPendingSmartNotes(db, projectPath) {
3364
+ const row = db.prepare("SELECT COUNT(*) AS cnt FROM notes WHERE project_path = ? AND type = 'smart' AND status = 'pending'").get(projectPath);
3365
+ return row?.cnt ?? 0;
3366
+ }
3367
+ function countUserMemoryCandidates(db) {
3368
+ const row = db.prepare("SELECT COUNT(*) AS cnt FROM user_memory_candidates").get();
3369
+ return row?.cnt ?? 0;
3370
+ }
3371
+ function countActivePrimers(db, projectPath) {
3372
+ const row = db.prepare("SELECT COUNT(*) AS cnt FROM primers WHERE project_path = ? AND status = 'active'").get(projectPath);
3373
+ return row?.cnt ?? 0;
3374
+ }
3375
+ function getDreamTaskBacklog(db, projectPath, task, options = {}) {
3376
+ switch (task) {
3377
+ case "map-memories": {
3378
+ const total = countActiveMemories(db, projectPath);
3379
+ return { pending: countUnmappedActiveMemories(db, projectPath), total };
3380
+ }
3381
+ case "verify": {
3382
+ return {
3383
+ pending: countUnverifiedMappedMemories(db, projectPath),
3384
+ total: countMappedMemories(db, projectPath)
3385
+ };
3386
+ }
3387
+ case "verify-broad": {
3388
+ const total = countMappedMemories(db, projectPath);
3389
+ return { pending: total, total };
3390
+ }
3391
+ case "curate": {
3392
+ const total = countActiveMemories(db, projectPath);
3393
+ return { pending: total, total };
3394
+ }
3395
+ case "compress-cues": {
3396
+ const total = countActiveMemories(db, projectPath);
3397
+ return { pending: countCueCandidates(db, projectPath), total };
3398
+ }
3399
+ case "classify-memories": {
3400
+ const total = countActiveMemories(db, projectPath);
3401
+ return { pending: countUnclassifiedActiveMemories(db, projectPath), total };
3402
+ }
3403
+ case "retrospective": {
3404
+ const pending = countProjectSessionsSince(db, projectPath, options.retrospectiveWatermarkMs ?? null);
3405
+ return { pending, total: pending };
3406
+ }
3407
+ case "maintain-docs": {
3408
+ const total = countCompartmentsSince(db, projectPath, 0);
3409
+ const pending = countCompartmentsSince(db, projectPath, options.lastRunAt ?? 0);
3410
+ return { pending, total };
3411
+ }
3412
+ case "evaluate-smart-notes": {
3413
+ const pending = countPendingSmartNotes(db, projectPath);
3414
+ return { pending, total: pending };
3415
+ }
3416
+ case "review-user-memories": {
3417
+ const pending = countUserMemoryCandidates(db);
3418
+ return { pending, total: pending };
3419
+ }
3420
+ case "promote-primers": {
3421
+ const pending = countPrimerCandidatesForProject(db, projectPath);
3422
+ return { pending, total: pending };
3423
+ }
3424
+ case "refresh-primers": {
3425
+ const total = countActivePrimers(db, projectPath);
3426
+ return { pending: countStalePrimers(db, projectPath), total };
3427
+ }
3428
+ default: {
3429
+ const _exhaustive = task;
3430
+ return _exhaustive;
3431
+ }
3432
+ }
3433
+ }
3434
+ function getDreamTaskBacklogs(db, projectPath, tasks = CANONICAL_DREAM_TASKS, options = {}) {
3435
+ const result = {};
3436
+ for (const task of tasks)
3437
+ result[task] = getDreamTaskBacklog(db, projectPath, task, options);
3438
+ return result;
3439
+ }
3440
+ function evaluateTaskGate(task, ctx) {
3441
+ const { db, projectIdentity: project, lastRunAt } = ctx;
3442
+ switch (task) {
3443
+ case "map-memories":
3444
+ return countUnmappedActiveMemories(db, project) > 0;
3445
+ case "verify":
3446
+ return countActiveMemories(db, project) > 0;
3447
+ case "verify-broad":
3448
+ return countActiveMemories(db, project) > 0;
3449
+ case "curate":
3450
+ return countActiveMemories(db, project) > 0;
3451
+ case "compress-cues":
3452
+ return countActiveMemories(db, project) > 0;
3453
+ case "classify-memories":
3454
+ return countActiveMemories(db, project) > 0;
3455
+ case "retrospective":
3456
+ return countProjectSessionsSince(db, project, ctx.retrospectiveWatermarkMs ?? null) > 0;
3457
+ case "maintain-docs":
3458
+ return countCompartmentsSince(db, project, lastRunAt ?? 0) > 0;
3459
+ case "evaluate-smart-notes":
3460
+ return getSmartNotesNeedingCompilation(db, project, Date.now(), 1).length > 0 || getStaleCompiledSmartNotes(db, project, Date.now(), 1).length > 0 || getPendingSmartNotes(db, project).some((note) => note.checkStatus === "fallback");
3461
+ case "review-user-memories":
3462
+ return getUserMemoryCandidates(db).length >= ctx.promotionThreshold;
3463
+ case "promote-primers":
3464
+ return countPrimerCandidatesForProject(db, project) >= (ctx.promotionThreshold ?? 2);
3465
+ case "refresh-primers":
3466
+ return getActivePrimers(db, project).some((primer) => !primer.answer.trim() || primer.answerRefreshedAt == null || (primer.lastObservedAt ?? 0) > primer.answerRefreshedAt);
3467
+ default: {
3468
+ const _exhaustive = task;
3469
+ return Boolean(_exhaustive);
3470
+ }
3471
+ }
3472
+ }
3473
+
2955
3474
  // ../plugin/src/features/magic-context/dreamer/open-opencode-db.ts
2956
3475
  import { existsSync as existsSync4 } from "node:fs";
2957
3476
  function openOpenCodeDb() {
@@ -2969,19 +3488,214 @@ function openOpenCodeDb() {
2969
3488
  return null;
2970
3489
  }
2971
3490
  }
2972
-
2973
- // ../plugin/src/features/magic-context/dreamer/task-executor.ts
2974
- import { createHash as createHash6 } from "node:crypto";
2975
- import { existsSync as existsSync7 } from "node:fs";
2976
-
2977
- // ../plugin/src/agents/dreamer.ts
2978
- var DREAMER_AGENT = "dreamer";
2979
- var DREAMER_RETROSPECTIVE_AGENT = "dreamer-retrospective";
2980
- var DREAMER_PRIMER_INVESTIGATOR_AGENT = "dreamer-primer-investigator";
2981
- var DREAMER_MEMORY_MAPPER_AGENT = "dreamer-memory-mapper";
2982
- var DREAMER_CLASSIFIER_AGENT = "dreamer-classifier";
2983
- var DREAMER_DOCS_AGENT = "dreamer-docs";
2984
- var DREAMER_REVIEWER_AGENT = "dreamer-reviewer";
3491
+
3492
+ // ../plugin/src/features/magic-context/dreamer/task-executor.ts
3493
+ import { createHash as createHash6 } from "node:crypto";
3494
+ import { existsSync as existsSync7 } from "node:fs";
3495
+
3496
+ // ../plugin/src/agents/dreamer.ts
3497
+ var DREAMER_AGENT = "dreamer";
3498
+ var DREAMER_RETROSPECTIVE_AGENT = "dreamer-retrospective";
3499
+ var DREAMER_PRIMER_INVESTIGATOR_AGENT = "dreamer-primer-investigator";
3500
+ var DREAMER_MEMORY_MAPPER_AGENT = "dreamer-memory-mapper";
3501
+ var DREAMER_CLASSIFIER_AGENT = "dreamer-classifier";
3502
+ var DREAMER_DOCS_AGENT = "dreamer-docs";
3503
+ var DREAMER_REVIEWER_AGENT = "dreamer-reviewer";
3504
+
3505
+ // ../plugin/src/hooks/magic-context/send-session-notification.ts
3506
+ var MAX_QUEUED_IGNORED_NOTIFICATIONS = 16;
3507
+ var queuedIgnoredNotifications = new Map;
3508
+ var flushingIgnoredNotifications = new Set;
3509
+ var midTurnDetector = (sessionId) => isMidTurn(undefined, sessionId);
3510
+ function queueIgnoredNotification(notification) {
3511
+ const queued = queuedIgnoredNotifications.get(notification.sessionId) ?? [];
3512
+ queued.push(notification);
3513
+ if (queued.length > MAX_QUEUED_IGNORED_NOTIFICATIONS) {
3514
+ queued.splice(0, queued.length - MAX_QUEUED_IGNORED_NOTIFICATIONS);
3515
+ sessionLog(notification.sessionId, `ignored notification queue full; dropped oldest entries (kept newest ${MAX_QUEUED_IGNORED_NOTIFICATIONS})`);
3516
+ }
3517
+ queuedIgnoredNotifications.set(notification.sessionId, queued);
3518
+ }
3519
+ async function trySendTuiToast(sessionId, text, params, forcePersist) {
3520
+ if (forcePersist)
3521
+ return false;
3522
+ const title = extractToastTitle(text);
3523
+ const message = text.length > 200 ? `${text.slice(0, 200)}…` : text;
3524
+ const toastVariant = inferToastVariant(text);
3525
+ const duration = params.toastDurationMs ?? 5000;
3526
+ const { isTuiConnected: checkTui } = await import("./rpc-notifications-s2rw51c6.js");
3527
+ if (!checkTui(sessionId))
3528
+ return false;
3529
+ try {
3530
+ const { pushNotification: pushNotification2 } = await import("./rpc-notifications-s2rw51c6.js");
3531
+ pushNotification2("toast", {
3532
+ title,
3533
+ message,
3534
+ variant: toastVariant,
3535
+ duration
3536
+ }, sessionId);
3537
+ return true;
3538
+ } catch {
3539
+ sessionLog(sessionId, "TUI RPC toast enqueue failed, falling back to ignored message");
3540
+ return false;
3541
+ }
3542
+ }
3543
+ function hasNotificationSessionClient(client) {
3544
+ if (client === null || typeof client !== "object")
3545
+ return false;
3546
+ const candidate = client;
3547
+ if (candidate.session === undefined)
3548
+ return true;
3549
+ if (candidate.session === null || typeof candidate.session !== "object")
3550
+ return false;
3551
+ const session = candidate.session;
3552
+ return (session.prompt === undefined || typeof session.prompt === "function") && (session.promptAsync === undefined || typeof session.promptAsync === "function");
3553
+ }
3554
+ function inferToastVariant(text) {
3555
+ const lower = text.toLowerCase();
3556
+ if (lower.includes("error") || lower.includes("failed") || lower.includes("alert"))
3557
+ return "error";
3558
+ if (lower.includes("warning") || lower.includes("⚠"))
3559
+ return "warning";
3560
+ if (lower.includes("complete") || lower.includes("success") || lower.includes("✓") || lower.includes("finished"))
3561
+ return "success";
3562
+ return "info";
3563
+ }
3564
+ function extractToastTitle(text) {
3565
+ const headingMatch = text.match(/^#+\s+(.+)/m);
3566
+ if (headingMatch)
3567
+ return headingMatch[1].trim();
3568
+ const firstLine = text.split(`
3569
+ `)[0].trim();
3570
+ if (firstLine.length <= 80)
3571
+ return firstLine;
3572
+ return "Magic Context";
3573
+ }
3574
+ async function sendIgnoredMessageNow(client, sessionId, text, params, forcePersist) {
3575
+ if (midTurnDetector(sessionId)) {
3576
+ queueIgnoredNotification({ client, sessionId, text, params, forcePersist });
3577
+ return "queued";
3578
+ }
3579
+ const { waitForSafeNotificationTarget } = await import("./safe-notification-target-ng8ygena.js");
3580
+ if (await waitForSafeNotificationTarget(client, sessionId) === "skip") {
3581
+ sessionLog(sessionId, "notification skipped (session not titled yet)");
3582
+ return "skipped";
3583
+ }
3584
+ if (midTurnDetector(sessionId)) {
3585
+ queueIgnoredNotification({ client, sessionId, text, params, forcePersist });
3586
+ return "queued";
3587
+ }
3588
+ if (!hasNotificationSessionClient(client)) {
3589
+ sessionLog(sessionId, "session prompt API unavailable for notification");
3590
+ return "failed";
3591
+ }
3592
+ const c = client;
3593
+ let agent = params.agent || undefined;
3594
+ let variant = params.variant || undefined;
3595
+ let model = params.providerId && params.modelId ? { providerID: params.providerId, modelID: params.modelId } : undefined;
3596
+ if (!agent || !model || !variant) {
3597
+ try {
3598
+ const { resolvePromptContext } = await import("./prompt-context-602yq94t.js");
3599
+ const resolved = await resolvePromptContext(client, sessionId);
3600
+ if (resolved) {
3601
+ agent = agent ?? resolved.agent;
3602
+ model = model ?? resolved.model;
3603
+ variant = variant ?? resolved.variant;
3604
+ }
3605
+ } catch {}
3606
+ }
3607
+ if (midTurnDetector(sessionId)) {
3608
+ queueIgnoredNotification({ client, sessionId, text, params, forcePersist });
3609
+ return "queued";
3610
+ }
3611
+ const input = {
3612
+ path: { id: sessionId },
3613
+ body: {
3614
+ noReply: true,
3615
+ agent,
3616
+ model,
3617
+ variant,
3618
+ parts: [
3619
+ {
3620
+ type: "text",
3621
+ text,
3622
+ ignored: true
3623
+ }
3624
+ ]
3625
+ }
3626
+ };
3627
+ try {
3628
+ if (typeof c.session?.prompt === "function") {
3629
+ await Promise.resolve(c.session.prompt(input));
3630
+ return "sent";
3631
+ }
3632
+ if (typeof c.session?.promptAsync === "function") {
3633
+ await c.session.promptAsync(input);
3634
+ return "sent";
3635
+ }
3636
+ sessionLog(sessionId, "session prompt API unavailable for notification");
3637
+ return "failed";
3638
+ } catch (error) {
3639
+ const msg = getErrorMessage(error);
3640
+ sessionLog(sessionId, "failed to send notification:", msg);
3641
+ return "failed";
3642
+ }
3643
+ }
3644
+ async function sendIgnoredMessage(client, sessionId, text, params, forcePersist = false) {
3645
+ if (await trySendTuiToast(sessionId, text, params, forcePersist))
3646
+ return "sent";
3647
+ if (midTurnDetector(sessionId)) {
3648
+ queueIgnoredNotification({ client, sessionId, text, params, forcePersist });
3649
+ return "queued";
3650
+ }
3651
+ return sendIgnoredMessageNow(client, sessionId, text, params, forcePersist);
3652
+ }
3653
+
3654
+ // ../plugin/src/hooks/magic-context/child-session-spawn.ts
3655
+ var STALE_PLUGIN_RESTART_NOTICE = "Magic Context: plugin build is older than its database — restart OpenCode";
3656
+ async function surfaceStalePluginBuild(args) {
3657
+ if (!args.parentSessionId)
3658
+ return;
3659
+ try {
3660
+ if (args.db) {
3661
+ updateSessionMeta(args.db, args.parentSessionId, {
3662
+ lastTransformError: STALE_PLUGIN_RESTART_NOTICE
3663
+ });
3664
+ }
3665
+ pushNotification("toast", {
3666
+ title: "Magic Context",
3667
+ message: STALE_PLUGIN_RESTART_NOTICE,
3668
+ variant: "error",
3669
+ duration: 1e4
3670
+ }, args.parentSessionId);
3671
+ pushNotification("action", { action: "refresh-sidebar" }, args.parentSessionId);
3672
+ await sendIgnoredMessage(args.client, args.parentSessionId, STALE_PLUGIN_RESTART_NOTICE, args.notificationParams ?? {}, true);
3673
+ } catch (error) {
3674
+ sessionLog(args.parentSessionId, `stale plugin-build warning delivery failed: ${error instanceof Error ? error.message : String(error)}`);
3675
+ }
3676
+ }
3677
+ async function createChildSessionWithFence(args) {
3678
+ const verdict = probeChildSpawnFence(args.db);
3679
+ if (!verdict.allowSpawn) {
3680
+ if (args.parentSessionId) {
3681
+ 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}`);
3682
+ }
3683
+ if (verdict.shouldSurface) {
3684
+ if (args.onFenceLatched)
3685
+ await args.onFenceLatched(verdict.failure);
3686
+ else
3687
+ await surfaceStalePluginBuild(args);
3688
+ }
3689
+ return null;
3690
+ }
3691
+ return args.client.session.create({
3692
+ body: {
3693
+ ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
3694
+ title: args.title
3695
+ },
3696
+ query: { directory: args.directory }
3697
+ });
3698
+ }
2985
3699
  // ../plugin/src/shared/model-suggestion-retry.ts
2986
3700
  var ABORT_CALL_TIMEOUT_MS = 3000;
2987
3701
  function copyPromptArgs(args, body) {
@@ -3635,7 +4349,7 @@ function parseCuesManifest(text) {
3635
4349
  const body = extractCompleteManifestBody(text, "cues");
3636
4350
  const out = [];
3637
4351
  for (const match of body.matchAll(/<cue\s+id="(\d+)"\s*>([\s\S]*?)<\/cue>/g)) {
3638
- const id = Number.parseInt(match[1], 10);
4352
+ const id = Number.parseInt(match[1] ?? "", 10);
3639
4353
  if (!Number.isInteger(id))
3640
4354
  continue;
3641
4355
  out.push({ id, cue: unescapeXml(match[2] ?? "").trim() });
@@ -3736,11 +4450,15 @@ async function runCompressCues(args) {
3736
4450
  break;
3737
4451
  }
3738
4452
  const sliceMs = computeChunkSliceMs(remainingMs, chunks.length - i);
3739
- const outcome = await compressOneChunk(args, chunks[i], sliceMs, abortController.signal);
4453
+ const chunk = chunks[i];
4454
+ if (!chunk)
4455
+ break;
4456
+ const outcome = await compressOneChunk(args, chunk, sliceMs, abortController.signal);
3740
4457
  result.compressed += outcome.compressed;
3741
4458
  result.skipped += outcome.skipped;
3742
4459
  result.remaining -= outcome.compressed;
3743
4460
  result.chunks += 1;
4461
+ args.onProgress?.(result.compressed + result.skipped);
3744
4462
  if (outcome.failure?.class === "timeout") {
3745
4463
  consecutiveTimeouts += 1;
3746
4464
  timeoutStreakElapsedMs.push(outcome.failure.elapsedMs);
@@ -3771,12 +4489,12 @@ async function compressOneChunk(args, chunk, sliceMs, signal) {
3771
4489
  projectPath: args.projectIdentity,
3772
4490
  memories: chunk.map(toPromptMemory)
3773
4491
  });
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 }
4492
+ const createResponse = await createChildSessionWithFence({
4493
+ client: args.client,
4494
+ db: args.db,
4495
+ parentSessionId: args.parentSessionId,
4496
+ title: "magic-context-dream-compress-cues",
4497
+ directory: args.sessionDirectory
3780
4498
  });
3781
4499
  const created = normalizeSDKResponse(createResponse, null, {
3782
4500
  preferResponseOnMissingData: true
@@ -4260,12 +4978,12 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
4260
4978
  abortController.abort();
4261
4979
  });
4262
4980
  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 }
4981
+ const createResponse = await createChildSessionWithFence({
4982
+ client: args.client,
4983
+ db: args.db,
4984
+ parentSessionId: args.parentSessionId,
4985
+ title: "magic-context-dream-user-memories",
4986
+ directory: args.sessionDirectory
4269
4987
  });
4270
4988
  const created = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
4271
4989
  agentSessionId = typeof created?.id === "string" ? created.id : null;
@@ -4836,6 +5554,7 @@ async function runClassify(args) {
4836
5554
  result.changed += counts.changed;
4837
5555
  result.remaining -= counts.classified;
4838
5556
  result.chunks += 1;
5557
+ args.onProgress?.(result.classified);
4839
5558
  }
4840
5559
  result.complete = result.remaining === 0;
4841
5560
  log(`[dreamer] classify: stage=${stage} classified=${result.classified} changed=${result.changed} chunks=${result.chunks} remaining=${result.remaining} complete=${result.complete}`);
@@ -4859,12 +5578,12 @@ async function classifyOneChunk(args, chunk, anchors, sliceMs, signal) {
4859
5578
  recordInvocation(args, startedAt, { status: "completed" });
4860
5579
  return run2;
4861
5580
  }
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 }
5581
+ const createResponse = await createChildSessionWithFence({
5582
+ client: args.client,
5583
+ db: args.db,
5584
+ parentSessionId: args.parentSessionId,
5585
+ title: "magic-context-dream-classify",
5586
+ directory: args.sessionDirectory
4868
5587
  });
4869
5588
  const created = normalizeSDKResponse(createResponse, null, {
4870
5589
  preferResponseOnMissingData: true
@@ -5096,64 +5815,6 @@ import { lookup as dnsLookup } from "node:dns/promises";
5096
5815
  import * as https from "node:https";
5097
5816
  import { isIP } from "node:net";
5098
5817
  import { domainToASCII } from "node:url";
5099
-
5100
- // ../plugin/src/features/magic-context/smart-notes/types.ts
5101
- var SMART_NOTE_CHECK_POLICY_VERSION = 1;
5102
- var SMART_NOTE_CHECK_FLOOR_MS = 5 * 60 * 1000;
5103
- var SMART_NOTE_CHECK_CEILING_MS = 24 * 60 * 60 * 1000;
5104
- var SMART_NOTE_CHECK_DEFAULT_INTERVAL_MS = 60 * 60 * 1000;
5105
- var SMART_NOTE_CHECK_MAX_STALENESS_MS = 7 * 24 * 60 * 60 * 1000;
5106
- var SMART_NOTE_CHECK_LIVENESS_RECHECK_MS = 24 * 60 * 60 * 1000;
5107
-
5108
- class SmartNoteNetworkError extends Error {
5109
- isSmartNoteNetworkError = true;
5110
- terminal;
5111
- constructor(message, options = {}) {
5112
- super(message);
5113
- this.name = "SmartNoteNetworkError";
5114
- this.terminal = options.terminal ?? false;
5115
- }
5116
- }
5117
-
5118
- class SmartNoteSecurityError extends Error {
5119
- isSmartNoteSecurityError = true;
5120
- constructor(message) {
5121
- super(message);
5122
- this.name = "SmartNoteSecurityError";
5123
- }
5124
- }
5125
- function isSmartNoteNetworkError(error) {
5126
- return error instanceof SmartNoteNetworkError || error instanceof Error && (error.name === "SmartNoteNetworkError" || error.message.includes("SmartNoteNetworkError") || error.message.includes("SMART_NOTE_NETWORK"));
5127
- }
5128
- function isTerminalSmartNoteNetworkError(error) {
5129
- return error instanceof SmartNoteNetworkError && error.terminal;
5130
- }
5131
- function parseSmartNoteManifest(json) {
5132
- if (!json)
5133
- return { capabilities: [] };
5134
- try {
5135
- const parsed = JSON.parse(json);
5136
- const capabilities = Array.isArray(parsed.capabilities) ? parsed.capabilities.filter((c) => ["readFile", "gitHeadSha", "gitTag", "gitLog", "httpGet"].includes(String(c))) : [];
5137
- return {
5138
- capabilities,
5139
- readFiles: stringArray(parsed.readFiles),
5140
- hosts: stringArray(parsed.hosts),
5141
- urls: stringArray(parsed.urls),
5142
- signals: stringArray(parsed.signals),
5143
- summary: typeof parsed.summary === "string" ? parsed.summary : undefined
5144
- };
5145
- } catch {
5146
- return { capabilities: [] };
5147
- }
5148
- }
5149
- function stringArray(value) {
5150
- if (!Array.isArray(value))
5151
- return;
5152
- const arr = value.filter((item) => typeof item === "string");
5153
- return arr.length > 0 ? arr : undefined;
5154
- }
5155
-
5156
- // ../plugin/src/features/magic-context/smart-notes/ssrf-guard.ts
5157
5818
  var DNS_TIMEOUT_MS = 3000;
5158
5819
  var DEFAULT_HTTP_TIMEOUT_MS = 5000;
5159
5820
  var DEFAULT_HTTP_BODY_LIMIT_BYTES = 64 * 1024;
@@ -5676,7 +6337,7 @@ function getAsyncModule() {
5676
6337
  asyncModulePromise ??= (async () => {
5677
6338
  const [{ default: singlefileAsyncifyVariant }, { newQuickJSAsyncWASMModuleFromVariant }] = await Promise.all([
5678
6339
  import("./index-ayd60xnw.js").then((m)=>__toESM(m.default,1)),
5679
- import("./index-v7fmc1s2.js")
6340
+ import("./index-3pnd4qh9.js")
5680
6341
  ]);
5681
6342
  return newQuickJSAsyncWASMModuleFromVariant(singlefileAsyncifyVariant);
5682
6343
  })();
@@ -5895,12 +6556,12 @@ Remember: output only the JSON object described by the system prompt.`;
5895
6556
  });
5896
6557
  };
5897
6558
  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 }
6559
+ const createResponse = await createChildSessionWithFence({
6560
+ client: args.client,
6561
+ db: args.db ?? null,
6562
+ parentSessionId: args.parentSessionId,
6563
+ title: `magic-context-smart-note-compile-${args.note.id}`,
6564
+ directory: args.sessionDirectory ?? args.projectIdentity
5904
6565
  });
5905
6566
  const created = normalizeSDKResponse(createResponse, null, {
5906
6567
  preferResponseOnMissingData: true
@@ -6077,213 +6738,66 @@ function capabilityUses(code) {
6077
6738
  const uses = new Set;
6078
6739
  const regex = /\bcap\s*\.\s*(readFile|gitHeadSha|gitTag|gitLog|httpGet)\s*\(/g;
6079
6740
  for (const match of code.matchAll(regex))
6080
- uses.add(match[1]);
6081
- return uses;
6082
- }
6083
- function literalCalls(code, method) {
6084
- const regex = new RegExp(`\\bcap\\s*\\.\\s*${method}\\s*\\(\\s*(["'])((?:\\\\.|(?!\\1)[^\\\\])*)\\1`, "g");
6085
- const values = [];
6086
- for (const match of code.matchAll(regex)) {
6087
- values.push(match[2].replace(/\\([\\"'])/g, "$1"));
6088
- }
6089
- return values;
6090
- }
6091
- function normalizeCron(cron) {
6092
- if (cron.length > MAX_CRON_CHARS)
6093
- throw new Error("check_cron exceeds 256 characters");
6094
- const normalized = cron.trim() || "0 * * * *";
6095
- const parsed = parseCron(normalized);
6096
- if (!parsed.ok)
6097
- throw new Error(`invalid check_cron: ${parsed.error}`);
6098
- const next = nextOccurrence(parsed.cron, new Date, undefined, SMART_NOTE_CHECK_CEILING_MS);
6099
- if (!next)
6100
- throw new Error("check_cron has no occurrence within the scheduling ceiling");
6101
- return normalized;
6102
- }
6103
- function unique(items) {
6104
- return [...new Set(items)];
6105
- }
6106
- function uniqueStrings(items, normalize = (value) => value) {
6107
- if (!Array.isArray(items))
6108
- return;
6109
- const values = unique(items.slice(0, MAX_MANIFEST_ENTRIES).filter((item) => typeof item === "string" && item.length > 0).map(normalize));
6110
- return values.length > 0 ? values : undefined;
6111
- }
6112
- function boundedError(error) {
6113
- return error.slice(0, MAX_COMPILER_ERROR_CHARS);
6114
- }
6115
-
6116
- // ../plugin/src/features/magic-context/smart-notes/schedule.ts
6117
- function nextSmartNoteCheckDueAt(cron, options = {}) {
6118
- const now = options.now ?? Date.now();
6119
- const floorMs = options.floorMs ?? SMART_NOTE_CHECK_FLOOR_MS;
6120
- const ceilingMs = options.ceilingMs ?? SMART_NOTE_CHECK_CEILING_MS;
6121
- const rawNext = cron?.trim() ? nextDueAtMs(cron, now, undefined, ceilingMs) : null;
6122
- const rawDelta = rawNext ? rawNext - now : SMART_NOTE_CHECK_DEFAULT_INTERVAL_MS;
6123
- const clamped = Math.min(ceilingMs, Math.max(floorMs, rawDelta));
6124
- const jittered = clamped + deterministicJitterMs(clamped, options.noteId, options.hash);
6125
- const bounded = Math.min(ceilingMs, Math.max(floorMs, jittered));
6126
- return now + bounded;
6127
- }
6128
- function deterministicJitterMs(intervalMs, noteId, hash) {
6129
- const max = Math.min(60000, Math.floor(intervalMs * 0.1));
6130
- if (max <= 0)
6131
- return 0;
6132
- const seed = `${noteId ?? 0}:${hash ?? ""}`;
6133
- let h = 2166136261;
6134
- for (let i = 0;i < seed.length; i++) {
6135
- h ^= seed.charCodeAt(i);
6136
- h = Math.imul(h, 16777619);
6137
- }
6138
- const unsigned = h >>> 0;
6139
- return unsigned % (max * 2 + 1) - max;
6140
- }
6141
-
6142
- // ../plugin/src/features/magic-context/smart-notes/storage.ts
6143
- function toSmartNote(note) {
6144
- return {
6145
- ...note,
6146
- checkStatus: note.checkStatus ?? "uncompiled",
6147
- checkFailureCount: note.checkFailureCount ?? 0,
6148
- checkNetworkFailureCount: note.checkNetworkFailureCount ?? 0,
6149
- policyVersion: note.policyVersion ?? 0
6150
- };
6151
- }
6152
- function commitSmartNoteState(db, args) {
6153
- db.exec("BEGIN IMMEDIATE");
6154
- let leaseLost = false;
6155
- let committed = false;
6156
- try {
6157
- if (args.leaseHeld && !args.leaseHeld()) {
6158
- leaseLost = true;
6159
- } else if (claimExpectedState(db, args.expected)) {
6160
- args.write();
6161
- committed = true;
6162
- }
6163
- db.exec("COMMIT");
6164
- } catch (error) {
6165
- try {
6166
- db.exec("ROLLBACK");
6167
- } catch {}
6168
- throw error;
6169
- }
6170
- if (leaseLost) {
6171
- throw new Error(`Dream lease lost during smart-note ${args.phase} commit`);
6172
- }
6173
- if (!committed) {
6174
- log(`[debug] smart note #${args.expected.noteId}: discarded stale ${args.phase} result`);
6175
- }
6176
- return committed;
6177
- }
6178
- function claimExpectedState(db, expected) {
6179
- if (expected.kind === "compiled-check") {
6180
- return db.prepare(`UPDATE notes SET id = id
6181
- WHERE id = ? AND type = 'smart' AND status = 'pending'
6182
- AND check_status = 'compiled' AND compiled_check = ?
6183
- AND check_hash IS ? AND check_compiled_at IS ?`).run(expected.noteId, expected.compiledCheck, expected.checkHash, expected.checkCompiledAt).changes > 0;
6184
- }
6185
- const statusClause = expected.checkStatus ? " AND check_status = ?" : "";
6186
- const params = [
6187
- expected.noteId,
6188
- expected.content,
6189
- expected.surfaceCondition,
6190
- expected.updatedAt
6191
- ];
6192
- if (expected.checkStatus)
6193
- params.push(expected.checkStatus);
6194
- return db.prepare(`UPDATE notes SET id = id
6195
- WHERE id = ? AND type = 'smart' AND status = 'pending'
6196
- AND content = ? AND surface_condition IS ? AND updated_at = ?${statusClause}`).run(...params).changes > 0;
6197
- }
6198
- function getDueCompiledSmartNoteChecks(db, projectPath, now, limit) {
6199
- return getPendingSmartNotes(db, projectPath).map(toSmartNote).filter((note) => note.checkStatus === "compiled" && note.compiledCheck !== null && note.policyVersion === SMART_NOTE_CHECK_POLICY_VERSION && (note.checkQuarantinedUntil === null || note.checkQuarantinedUntil <= now) && (note.checkNextDueAt === null || note.checkNextDueAt <= now)).sort((a, b) => (a.checkNextDueAt ?? 0) - (b.checkNextDueAt ?? 0) || a.id - b.id).slice(0, Math.max(1, limit));
6200
- }
6201
- function getSmartNotesNeedingCompilation(db, projectPath, now, limit) {
6202
- return getPendingSmartNotes(db, projectPath).map(toSmartNote).filter((note) => (note.checkNextDueAt === null || note.checkNextDueAt <= now) && (note.checkStatus === "uncompiled" || note.checkStatus === "failing" || note.compiledCheck === null || note.policyVersion !== SMART_NOTE_CHECK_POLICY_VERSION)).sort((a, b) => a.createdAt - b.createdAt || a.id - b.id).slice(0, Math.max(1, limit));
6203
- }
6204
- function getStaleCompiledSmartNotes(db, projectPath, now, limit) {
6205
- const staleBefore = now - SMART_NOTE_CHECK_MAX_STALENESS_MS;
6206
- const livenessBefore = now - SMART_NOTE_CHECK_LIVENESS_RECHECK_MS;
6207
- return getPendingSmartNotes(db, projectPath).map(toSmartNote).filter((note) => note.checkStatus === "compiled" && note.compiledCheck !== null && note.policyVersion === SMART_NOTE_CHECK_POLICY_VERSION && note.checkFalseSinceAt !== null && note.checkFalseSinceAt <= staleBefore && (note.checkLastLivenessAt === null || note.checkLastLivenessAt <= livenessBefore)).sort((a, b) => (a.checkFalseSinceAt ?? 0) - (b.checkFalseSinceAt ?? 0) || a.id - b.id).slice(0, Math.max(1, limit));
6208
- }
6209
- function storeCompiledSmartNoteCheck(db, args) {
6210
- db.prepare(`UPDATE notes
6211
- SET compiled_check = ?,
6212
- manifest_json = ?,
6213
- check_hash = ?,
6214
- check_cron = ?,
6215
- check_version = 1,
6216
- check_status = 'compiled',
6217
- check_failure_count = 0,
6218
- check_network_failure_count = 0,
6219
- check_quarantined_until = NULL,
6220
- check_next_due_at = ?,
6221
- check_compiled_at = ?,
6222
- check_false_since_at = COALESCE(check_false_since_at, ?),
6223
- check_last_liveness_at = NULL,
6224
- policy_version = ?,
6225
- updated_at = ?
6226
- WHERE id = ? AND type = 'smart'`).run(args.compiledCheck, JSON.stringify(args.manifest), args.checkHash, args.checkCron, args.nextDueAt, args.now, args.now, SMART_NOTE_CHECK_POLICY_VERSION, args.now, args.noteId);
6227
- }
6228
- function markCompiledCheckFalse(db, noteId, nextDueAt, now) {
6229
- db.prepare(`UPDATE notes
6230
- SET last_checked_at = ?,
6231
- updated_at = ?,
6232
- check_next_due_at = ?,
6233
- check_failure_count = 0,
6234
- check_network_failure_count = 0,
6235
- check_false_since_at = COALESCE(check_false_since_at, ?)
6236
- WHERE id = ? AND type = 'smart'`).run(now, now, nextDueAt, now, noteId);
6741
+ uses.add(match[1]);
6742
+ return uses;
6237
6743
  }
6238
- function markCompiledCheckLogicFailure(db, noteId, now, maxFailures) {
6239
- const failureCount = readFailureCount(db, noteId, "check_failure_count") + 1;
6240
- const status = failureCount >= maxFailures ? "failing" : "compiled";
6241
- db.prepare(`UPDATE notes
6242
- SET check_failure_count = ?,
6243
- check_status = ?,
6244
- check_next_due_at = ?,
6245
- updated_at = ?
6246
- WHERE id = ? AND type = 'smart'`).run(failureCount, status, now + backoffMs(failureCount), now, noteId);
6744
+ function literalCalls(code, method) {
6745
+ const regex = new RegExp(`\\bcap\\s*\\.\\s*${method}\\s*\\(\\s*(["'])((?:\\\\.|(?!\\1)[^\\\\])*)\\1`, "g");
6746
+ const values = [];
6747
+ for (const match of code.matchAll(regex)) {
6748
+ values.push(match[2].replace(/\\([\\"'])/g, "$1"));
6749
+ }
6750
+ return values;
6247
6751
  }
6248
- function markCompiledCheckNetworkFailure(db, noteId, now, maxFailures) {
6249
- const failureCount = readFailureCount(db, noteId, "check_network_failure_count") + 1;
6250
- const quarantinedUntil = now + backoffMs(failureCount);
6251
- const status = failureCount >= maxFailures ? "failing" : "compiled";
6252
- db.prepare(`UPDATE notes
6253
- SET check_network_failure_count = ?,
6254
- check_status = ?,
6255
- check_next_due_at = ?,
6256
- check_quarantined_until = ?,
6257
- updated_at = ?
6258
- WHERE id = ? AND type = 'smart'`).run(failureCount, status, quarantinedUntil, quarantinedUntil, now, noteId);
6752
+ function normalizeCron(cron) {
6753
+ if (cron.length > MAX_CRON_CHARS)
6754
+ throw new Error("check_cron exceeds 256 characters");
6755
+ const normalized = cron.trim() || "0 * * * *";
6756
+ const parsed = parseCron(normalized);
6757
+ if (!parsed.ok)
6758
+ throw new Error(`invalid check_cron: ${parsed.error}`);
6759
+ const next = nextOccurrence(parsed.cron, new Date, undefined, SMART_NOTE_CHECK_CEILING_MS);
6760
+ if (!next)
6761
+ throw new Error("check_cron has no occurrence within the scheduling ceiling");
6762
+ return normalized;
6259
6763
  }
6260
- function markSmartNoteLivenessChecked(db, noteId, now) {
6261
- db.prepare(`UPDATE notes
6262
- SET check_last_liveness_at = ?, updated_at = ?
6263
- WHERE id = ? AND type = 'smart'`).run(now, now, noteId);
6764
+ function unique(items) {
6765
+ return [...new Set(items)];
6264
6766
  }
6265
- function markSmartNoteCheckStatus(db, noteId, status, now) {
6266
- db.prepare(`UPDATE notes SET check_status = ?, updated_at = ? WHERE id = ? AND type = 'smart'`).run(status, now, noteId);
6767
+ function uniqueStrings(items, normalize = (value) => value) {
6768
+ if (!Array.isArray(items))
6769
+ return;
6770
+ const values = unique(items.slice(0, MAX_MANIFEST_ENTRIES).filter((item) => typeof item === "string" && item.length > 0).map(normalize));
6771
+ return values.length > 0 ? values : undefined;
6267
6772
  }
6268
- function markSmartNoteCompilationFailure(db, noteId, now, maxFailures) {
6269
- const failureCount = readFailureCount(db, noteId, "check_failure_count") + 1;
6270
- const status = failureCount >= maxFailures ? "fallback" : "uncompiled";
6271
- db.prepare(`UPDATE notes
6272
- SET check_failure_count = ?,
6273
- check_status = ?,
6274
- check_next_due_at = ?,
6275
- updated_at = ?
6276
- WHERE id = ? AND type = 'smart'`).run(failureCount, status, now + backoffMs(failureCount), now, noteId);
6773
+ function boundedError(error) {
6774
+ return error.slice(0, MAX_COMPILER_ERROR_CHARS);
6277
6775
  }
6278
- function readFailureCount(db, noteId, column) {
6279
- if (column !== "check_failure_count" && column !== "check_network_failure_count")
6280
- return 0;
6281
- const row = db.prepare(`SELECT ${column} AS count FROM notes WHERE id = ?`).get(noteId);
6282
- return row?.count ?? 0;
6776
+
6777
+ // ../plugin/src/features/magic-context/smart-notes/schedule.ts
6778
+ function nextSmartNoteCheckDueAt(cron, options = {}) {
6779
+ const now = options.now ?? Date.now();
6780
+ const floorMs = options.floorMs ?? SMART_NOTE_CHECK_FLOOR_MS;
6781
+ const ceilingMs = options.ceilingMs ?? SMART_NOTE_CHECK_CEILING_MS;
6782
+ const rawNext = cron?.trim() ? nextDueAtMs(cron, now, undefined, ceilingMs) : null;
6783
+ const rawDelta = rawNext ? rawNext - now : SMART_NOTE_CHECK_DEFAULT_INTERVAL_MS;
6784
+ const clamped = Math.min(ceilingMs, Math.max(floorMs, rawDelta));
6785
+ const jittered = clamped + deterministicJitterMs(clamped, options.noteId, options.hash);
6786
+ const bounded = Math.min(ceilingMs, Math.max(floorMs, jittered));
6787
+ return now + bounded;
6283
6788
  }
6284
- function backoffMs(failureCount) {
6285
- const minutes = Math.min(24 * 60, 5 * 2 ** Math.max(0, failureCount - 1));
6286
- return minutes * 60 * 1000;
6789
+ function deterministicJitterMs(intervalMs, noteId, hash) {
6790
+ const max = Math.min(60000, Math.floor(intervalMs * 0.1));
6791
+ if (max <= 0)
6792
+ return 0;
6793
+ const seed = `${noteId ?? 0}:${hash ?? ""}`;
6794
+ let h = 2166136261;
6795
+ for (let i = 0;i < seed.length; i++) {
6796
+ h ^= seed.charCodeAt(i);
6797
+ h = Math.imul(h, 16777619);
6798
+ }
6799
+ const unsigned = h >>> 0;
6800
+ return unsigned % (max * 2 + 1) - max;
6287
6801
  }
6288
6802
 
6289
6803
  // ../plugin/src/features/magic-context/smart-notes/runner.ts
@@ -6681,12 +7195,12 @@ async function confirmReadOnly(args, noteId, content, surfaceCondition, leaseSig
6681
7195
  });
6682
7196
  };
6683
7197
  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 }
7198
+ const createResponse = await createChildSessionWithFence({
7199
+ client: args.client,
7200
+ db: args.db,
7201
+ parentSessionId: args.parentSessionId,
7202
+ title: `magic-context-smart-note-confirm-${noteId}`,
7203
+ directory: args.sessionDirectory ?? args.projectIdentity
6690
7204
  });
6691
7205
  const created = normalizeSDKResponse(createResponse, null, {
6692
7206
  preferResponseOnMissingData: true
@@ -7019,6 +7533,7 @@ async function mapMemories(args) {
7019
7533
  result.independent += counts.independent;
7020
7534
  result.remaining -= counts.mapped + counts.independent;
7021
7535
  result.batches += 1;
7536
+ args.onProgress?.(result.mapped + result.independent);
7022
7537
  }
7023
7538
  result.complete = result.remaining === 0;
7024
7539
  log(`[dreamer] map-memories: mapped=${result.mapped} independent=${result.independent} batches=${result.batches} remaining=${result.remaining} complete=${result.complete}`);
@@ -7031,12 +7546,12 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
7031
7546
  let agentSessionId = null;
7032
7547
  const startedAt = Date.now();
7033
7548
  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 }
7549
+ const createResponse = await createChildSessionWithFence({
7550
+ client: args.client,
7551
+ db: args.db,
7552
+ parentSessionId: args.parentSessionId,
7553
+ title: "magic-context-dream-map-memories",
7554
+ directory: args.sessionDirectory
7040
7555
  });
7041
7556
  const created = normalizeSDKResponse(createResponse, null, {
7042
7557
  preferResponseOnMissingData: true
@@ -7620,6 +8135,7 @@ async function refreshPrimers(args) {
7620
8135
  result.refreshed += 1;
7621
8136
  else
7622
8137
  result.skipped += 1;
8138
+ args.onProgress?.(result.refreshed + result.skipped);
7623
8139
  }
7624
8140
  log(`[dreamer] refresh-primers: refreshed=${result.refreshed} skipped=${result.skipped}`);
7625
8141
  return result;
@@ -7648,12 +8164,12 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
7648
8164
  let agentSessionId = null;
7649
8165
  const startedAt = Date.now();
7650
8166
  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 }
8167
+ const createResponse = await createChildSessionWithFence({
8168
+ client: args.client,
8169
+ db: args.db,
8170
+ parentSessionId: args.parentSessionId,
8171
+ title: "magic-context-dream-refresh-primers",
8172
+ directory: args.sessionDirectory
7657
8173
  });
7658
8174
  const created = normalizeSDKResponse(createResponse, null, {
7659
8175
  preferResponseOnMissingData: true
@@ -8151,8 +8667,8 @@ function extractToolRows(parts) {
8151
8667
  const record = part;
8152
8668
  if (record.type !== "tool" || typeof record.tool !== "string")
8153
8669
  continue;
8154
- const state = record.state;
8155
- const stateRecord = state && typeof state === "object" ? state : {};
8670
+ const state2 = record.state;
8671
+ const stateRecord = state2 && typeof state2 === "object" ? state2 : {};
8156
8672
  const output = stringifyToolOutput(stateRecord.output);
8157
8673
  const errorText = stringifyToolOutput(stateRecord.error);
8158
8674
  const status = typeof stateRecord.status === "string" ? stateRecord.status : "";
@@ -8467,6 +8983,7 @@ async function runVerify(args) {
8467
8983
  result.archived += counts.archived;
8468
8984
  result.remaining -= counts.verified + counts.updated + counts.archived;
8469
8985
  result.batches += 1;
8986
+ args.onProgress?.(result.verified + result.updated + result.archived);
8470
8987
  }
8471
8988
  result.complete = result.remaining === 0;
8472
8989
  log(`[dreamer] ${args.forceBroad ? "verify-broad" : "verify"}: verified=${result.verified} updated=${result.updated} archived=${result.archived} batches=${result.batches} remaining=${result.remaining} complete=${result.complete}`);
@@ -8479,12 +8996,12 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
8479
8996
  let agentSessionId = null;
8480
8997
  const startedAt = Date.now();
8481
8998
  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 }
8999
+ const createResponse = await createChildSessionWithFence({
9000
+ client: args.client,
9001
+ db: args.db,
9002
+ parentSessionId: args.parentSessionId,
9003
+ title: "magic-context-dream-verify",
9004
+ directory: args.sessionDirectory
8488
9005
  });
8489
9006
  const created = normalizeSDKResponse(createResponse, null, {
8490
9007
  preferResponseOnMissingData: true
@@ -8758,6 +9275,20 @@ function createDreamTaskExecutor(deps) {
8758
9275
  const { db, projectIdentity, holderId, leaseKey } = ctx;
8759
9276
  const startedAt = Date.now();
8760
9277
  const deadline = startedAt + config.timeoutMinutes * 60 * 1000;
9278
+ const backlogAtStart = getDreamTaskBacklog(db, projectIdentity, config.task);
9279
+ const reportProgress = (processed) => {
9280
+ deps.onProgress?.({
9281
+ task: config.task,
9282
+ processed: Math.max(0, processed),
9283
+ total: backlogAtStart.pending,
9284
+ startedAt
9285
+ });
9286
+ };
9287
+ reportProgress(0);
9288
+ const incompleteMessage = (remaining) => {
9289
+ const processed = processedDreamTaskItems(backlogAtStart.pending, remaining);
9290
+ return `${config.task} incomplete: ${remaining} remain (was ${backlogAtStart.pending} at run start; processed ${processed} this run)`;
9291
+ };
8761
9292
  const parent = await resolveParentSessionId();
8762
9293
  let moduleRoute;
8763
9294
  if (config.task === "map-memories" || config.task === "compress-cues" || config.task === "verify" || config.task === "verify-broad" || config.task === "retrospective") {
@@ -8786,7 +9317,19 @@ function createDreamTaskExecutor(deps) {
8786
9317
  name: config.task,
8787
9318
  durationMs: Date.now() - startedAt,
8788
9319
  resultChars: 0,
8789
- ...error ? { error } : {}
9320
+ ...error ? { error } : {},
9321
+ backlog: (() => {
9322
+ const end = getDreamTaskBacklog(db, projectIdentity, config.task);
9323
+ const processed = processedDreamTaskItems(backlogAtStart.pending, end.pending);
9324
+ const value = {
9325
+ pendingAtStart: backlogAtStart.pending,
9326
+ totalAtStart: backlogAtStart.total,
9327
+ pendingAtEnd: end.pending,
9328
+ totalAtEnd: end.total,
9329
+ processed
9330
+ };
9331
+ return value;
9332
+ })()
8790
9333
  }
8791
9334
  ],
8792
9335
  tasksSucceeded: status === "completed" ? 1 : 0,
@@ -8820,8 +9363,8 @@ function createDreamTaskExecutor(deps) {
8820
9363
  }
8821
9364
  try {
8822
9365
  if (config.task === "compress-cues") {
8823
- if (deps.experimentalMural?.enabled !== true) {
8824
- log("[dreamer] compress-cues: skipped (experimental.mural is not enabled)");
9366
+ if (deps.mural?.enabled !== true) {
9367
+ log("[dreamer] compress-cues: skipped (mural is not enabled)");
8825
9368
  recordRun("completed", null);
8826
9369
  return { status: "completed" };
8827
9370
  }
@@ -8840,12 +9383,13 @@ function createDreamTaskExecutor(deps) {
8840
9383
  holderId,
8841
9384
  leaseKey,
8842
9385
  deadline,
8843
- model: config.model ?? deps.experimentalMural.model ?? deps.dreamerModel,
8844
- fallbackModels: config.fallbackModels
9386
+ model: config.model ?? deps.mural.model ?? deps.dreamerModel,
9387
+ fallbackModels: config.fallbackModels,
9388
+ onProgress: (processed) => reportProgress(processed)
8845
9389
  });
8846
9390
  log(`[dreamer] compress-cues: compressed=${result.compressed} skipped=${result.skipped} chunks=${result.chunks} remaining=${result.remaining}`);
8847
9391
  if (!result.complete) {
8848
- const error = `compress-cues incomplete: ${result.remaining} selected memories remain`;
9392
+ const error = incompleteMessage(result.remaining);
8849
9393
  recordRun("failed", error);
8850
9394
  return { status: "failed", transient: true, error };
8851
9395
  }
@@ -8882,11 +9426,12 @@ function createDreamTaskExecutor(deps) {
8882
9426
  deadline,
8883
9427
  model: config.model,
8884
9428
  fallbackModels: config.fallbackModels,
8885
- moduleRoute
9429
+ moduleRoute,
9430
+ onProgress: (processed) => reportProgress(processed)
8886
9431
  });
8887
9432
  log(`[dreamer] map-memories: mapped=${result.mapped} independent=${result.independent} batches=${result.batches} remaining=${result.remaining}`);
8888
9433
  if (!result.complete) {
8889
- const error = `map-memories incomplete: ${result.remaining} selected memories remain`;
9434
+ const error = incompleteMessage(result.remaining);
8890
9435
  recordRun("failed", error);
8891
9436
  return { status: "failed", transient: true, error };
8892
9437
  }
@@ -8908,10 +9453,11 @@ function createDreamTaskExecutor(deps) {
8908
9453
  model: config.model,
8909
9454
  fallbackModels: config.fallbackModels,
8910
9455
  language: config.language ?? deps.language,
8911
- moduleRoute
9456
+ moduleRoute,
9457
+ onProgress: (processed) => reportProgress(processed)
8912
9458
  });
8913
9459
  if (!result.complete) {
8914
- const error = `${config.task} incomplete: ${result.remaining} selected memories remain`;
9460
+ const error = incompleteMessage(result.remaining);
8915
9461
  recordRun("failed", error, {
8916
9462
  memoryChanges: computeMemoryDelta(memoryBefore)
8917
9463
  });
@@ -8970,11 +9516,12 @@ function createDreamTaskExecutor(deps) {
8970
9516
  deadline,
8971
9517
  model: config.model,
8972
9518
  fallbackModels: config.fallbackModels,
8973
- ...moduleArgs
9519
+ ...moduleArgs,
9520
+ onProgress: (processed) => reportProgress(processed)
8974
9521
  });
8975
9522
  log(`[dreamer] classify-memories: stage=${result.stage} classified=${result.classified} changed=${result.changed} chunks=${result.chunks} remaining=${result.remaining}`);
8976
9523
  if (!result.complete) {
8977
- const error = `classify-memories incomplete: ${result.remaining} selected memories remain`;
9524
+ const error = incompleteMessage(result.remaining);
8978
9525
  recordRun("failed", error);
8979
9526
  return { status: "failed", transient: true, error };
8980
9527
  }
@@ -9010,7 +9557,8 @@ function createDreamTaskExecutor(deps) {
9010
9557
  model: config.model,
9011
9558
  fallbackModels: config.fallbackModels,
9012
9559
  language: config.language ?? deps.language,
9013
- rawProviderFactory: deps.primerRawProviderFactory
9560
+ rawProviderFactory: deps.primerRawProviderFactory,
9561
+ onProgress: (processed) => reportProgress(processed)
9014
9562
  });
9015
9563
  recordRun("completed", null);
9016
9564
  log(`[dreamer] refresh-primers: refreshed=${result.refreshed} skipped=${result.skipped}`);
@@ -9064,6 +9612,8 @@ function createDreamTaskExecutor(deps) {
9064
9612
  recordRun("failed", brief);
9065
9613
  log(`[dreamer] task ${config.task} failed (transient=${transient}): ${brief}`);
9066
9614
  return { status: "failed", transient, error: brief };
9615
+ } finally {
9616
+ deps.onProgress?.(null, config.task);
9067
9617
  }
9068
9618
  };
9069
9619
  }
@@ -9171,12 +9721,12 @@ async function runRetrospectiveTask(config, ctx, helpers) {
9171
9721
  });
9172
9722
  let childSessionId = null;
9173
9723
  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 }
9724
+ const createResponse = await createChildSessionWithFence({
9725
+ client: deps.client,
9726
+ db,
9727
+ parentSessionId: parent ?? undefined,
9728
+ title: "magic-context-dream-retrospective",
9729
+ directory: deps.sessionDirectory
9180
9730
  });
9181
9731
  const created = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
9182
9732
  childSessionId = typeof created?.id === "string" ? created.id : null;
@@ -9361,12 +9911,12 @@ async function runAgenticTask(config, ctx, helpers) {
9361
9911
  });
9362
9912
  let childSessionId = null;
9363
9913
  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 }
9914
+ const createResponse = await createChildSessionWithFence({
9915
+ client: deps.client,
9916
+ db,
9917
+ parentSessionId: parent ?? undefined,
9918
+ title: `magic-context-dream-${task}`,
9919
+ directory: docsDir
9370
9920
  });
9371
9921
  const created = normalizeSDKResponse(createResponse, null, {
9372
9922
  preferResponseOnMissingData: true
@@ -9439,66 +9989,6 @@ async function runAgenticTask(config, ctx, helpers) {
9439
9989
  }
9440
9990
  }
9441
9991
 
9442
- // ../plugin/src/features/magic-context/dreamer/task-gates.ts
9443
- function countActiveMemories(db, projectPath) {
9444
- const row = db.prepare("SELECT COUNT(*) AS cnt FROM memories WHERE project_path = ? AND status IN ('active','permanent')").get(projectPath);
9445
- return row?.cnt ?? 0;
9446
- }
9447
- function countUnmappedActiveMemories(db, projectPath) {
9448
- const row = db.prepare(`SELECT COUNT(*) AS cnt
9449
- FROM memories m
9450
- WHERE m.project_path = ?
9451
- AND m.status IN ('active','permanent')
9452
- AND NOT EXISTS (
9453
- SELECT 1 FROM memory_verifications v WHERE v.memory_id = m.id
9454
- )`).get(projectPath);
9455
- return row?.cnt ?? 0;
9456
- }
9457
- function countCompartmentsSince(db, projectPath, since) {
9458
- const row = db.prepare(`SELECT COUNT(*) AS cnt
9459
- FROM compartments c
9460
- JOIN session_projects sp ON sp.session_id = c.session_id
9461
- WHERE sp.project_path = ? AND c.created_at > ?`).get(projectPath, since);
9462
- return row?.cnt ?? 0;
9463
- }
9464
- function countProjectSessionsSince(db, projectPath, since) {
9465
- const row = since === null ? db.prepare("SELECT COUNT(*) AS cnt FROM session_projects WHERE project_path = ?").get(projectPath) : db.prepare("SELECT COUNT(*) AS cnt FROM session_projects WHERE project_path = ? AND updated_at > ?").get(projectPath, since);
9466
- return row?.cnt ?? 0;
9467
- }
9468
- function evaluateTaskGate(task, ctx) {
9469
- const { db, projectIdentity: project, lastRunAt } = ctx;
9470
- switch (task) {
9471
- case "map-memories":
9472
- return countUnmappedActiveMemories(db, project) > 0;
9473
- case "verify":
9474
- return countActiveMemories(db, project) > 0;
9475
- case "verify-broad":
9476
- return countActiveMemories(db, project) > 0;
9477
- case "curate":
9478
- return countActiveMemories(db, project) > 0;
9479
- case "compress-cues":
9480
- return countActiveMemories(db, project) > 0;
9481
- case "classify-memories":
9482
- return countActiveMemories(db, project) > 0;
9483
- case "retrospective":
9484
- return countProjectSessionsSince(db, project, ctx.retrospectiveWatermarkMs ?? null) > 0;
9485
- case "maintain-docs":
9486
- return countCompartmentsSince(db, project, lastRunAt ?? 0) > 0;
9487
- case "evaluate-smart-notes":
9488
- return getSmartNotesNeedingCompilation(db, project, Date.now(), 1).length > 0 || getStaleCompiledSmartNotes(db, project, Date.now(), 1).length > 0 || getPendingSmartNotes(db, project).some((note) => note.checkStatus === "fallback");
9489
- case "review-user-memories":
9490
- return getUserMemoryCandidates(db).length >= ctx.promotionThreshold;
9491
- case "promote-primers":
9492
- return countPrimerCandidatesForProject(db, project) >= (ctx.promotionThreshold ?? 2);
9493
- case "refresh-primers":
9494
- return getActivePrimers(db, project).some((primer) => !primer.answer.trim() || primer.answerRefreshedAt == null || (primer.lastObservedAt ?? 0) > primer.answerRefreshedAt);
9495
- default: {
9496
- const _exhaustive = task;
9497
- return Boolean(_exhaustive);
9498
- }
9499
- }
9500
- }
9501
-
9502
9992
  // ../plugin/src/features/magic-context/dreamer/task-scheduler.ts
9503
9993
  var MAX_TASK_RETRIES = 3;
9504
9994
  function ensureSeeded(db, projectIdentity, config, now) {
@@ -9538,11 +10028,11 @@ function planDueTasks(db, projectIdentity, tasks, now) {
9538
10028
  const due = [];
9539
10029
  for (const config of tasks) {
9540
10030
  reconcileSchedule(db, projectIdentity, config, now);
9541
- const state = getTaskScheduleState(db, projectIdentity, config.task);
9542
- if (!state || state.nextDueAt === null)
10031
+ const state2 = getTaskScheduleState(db, projectIdentity, config.task);
10032
+ if (!state2 || state2.nextDueAt === null)
9543
10033
  continue;
9544
- if (now >= state.nextDueAt) {
9545
- due.push({ config, scheduledAt: state.nextDueAt });
10034
+ if (now >= state2.nextDueAt) {
10035
+ due.push({ config, scheduledAt: state2.nextDueAt });
9546
10036
  }
9547
10037
  }
9548
10038
  return due;
@@ -9646,10 +10136,10 @@ async function runDomainGroup(deps, group, cb) {
9646
10136
  cb?.onRan?.(due.config.task);
9647
10137
  } else if (outcome.transient) {
9648
10138
  recordTransientFailure(db, projectIdentity, due, finishedAt, outcome.error ?? null);
9649
- cb?.onFailed?.(due.config.task);
10139
+ cb?.onFailed?.(due.config.task, outcome.error);
9650
10140
  } else {
9651
10141
  advanceAfterRun(db, projectIdentity, due, finishedAt, "failed", outcome.error ?? null);
9652
- cb?.onFailed?.(due.config.task);
10142
+ cb?.onFailed?.(due.config.task, outcome.error);
9653
10143
  }
9654
10144
  }
9655
10145
  } finally {
@@ -9658,7 +10148,15 @@ async function runDomainGroup(deps, group, cb) {
9658
10148
  }
9659
10149
  async function runManualDream(deps) {
9660
10150
  const now = Date.now();
9661
- const result = { ran: [], skippedNoWork: [], deferredBusy: [], failed: [] };
10151
+ const result = {
10152
+ ran: [],
10153
+ skippedNoWork: [],
10154
+ deferredBusy: [],
10155
+ failed: [],
10156
+ failureDetails: [],
10157
+ backlogBefore: {},
10158
+ backlogAfter: {}
10159
+ };
9662
10160
  let selected;
9663
10161
  let forceGate = false;
9664
10162
  if (deps.task) {
@@ -9672,6 +10170,9 @@ async function runManualDream(deps) {
9672
10170
  }
9673
10171
  if (selected.length === 0)
9674
10172
  return result;
10173
+ const selectedTaskNames = selected.map((config) => config.task);
10174
+ result.backlogBefore = getDreamTaskBacklogs(deps.db, deps.projectIdentity, selectedTaskNames);
10175
+ result.backlogAfter = { ...result.backlogBefore };
9675
10176
  for (const cfg of selected)
9676
10177
  ensureSeeded(deps.db, deps.projectIdentity, cfg, now);
9677
10178
  const dueAll = selected.map((config) => ({ config, scheduledAt: now }));
@@ -9693,8 +10194,10 @@ async function runManualDream(deps) {
9693
10194
  else
9694
10195
  result.skippedNoWork.push(d.config.task);
9695
10196
  }
9696
- if (gated.length === 0)
10197
+ if (gated.length === 0) {
10198
+ result.backlogAfter = getDreamTaskBacklogs(deps.db, deps.projectIdentity, selectedTaskNames);
9697
10199
  return result;
10200
+ }
9698
10201
  const groups = new Map;
9699
10202
  for (const d of gated) {
9700
10203
  const kind = leaseKindFor(d.config.task);
@@ -9706,9 +10209,14 @@ async function runManualDream(deps) {
9706
10209
  forceGate,
9707
10210
  leaseWaitMs: MANUAL_RUN_LEASE_WAIT_MS,
9708
10211
  onRan: (t) => result.ran.push(t),
9709
- onFailed: (t) => result.failed.push(t),
10212
+ onFailed: (task, error) => {
10213
+ result.failed.push(task);
10214
+ if (error)
10215
+ result.failureDetails?.push(`${task}: ${error}`);
10216
+ },
9710
10217
  onBusy: (t) => result.deferredBusy.push(t)
9711
10218
  })));
10219
+ result.backlogAfter = getDreamTaskBacklogs(deps.db, deps.projectIdentity, selectedTaskNames);
9712
10220
  return result;
9713
10221
  }
9714
10222
  async function runDueTasksForProject(deps) {
@@ -10023,10 +10531,11 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
10023
10531
  ensureProjectRegistered: reg.ensureRegistered,
10024
10532
  language: reg.language,
10025
10533
  dreamerModel: dreamerConfig.model,
10026
- experimentalMural: reg.experimentalMural,
10534
+ mural: reg.mural,
10027
10535
  memoryInjectionBudgetTokens: reg.memoryInjectionBudgetTokens,
10028
10536
  transformMode: reg.transformMode,
10029
- moduleClient: reg.moduleClient
10537
+ moduleClient: reg.moduleClient,
10538
+ onProgress: (progress, completedTask) => reg.onDreamerProgress?.(progress, completedTask)
10030
10539
  });
10031
10540
  const ran = await runDueTasksForProject({
10032
10541
  db,
@@ -10696,13 +11205,18 @@ Unknown task "${requested}".`,
10696
11205
  });
10697
11206
  return;
10698
11207
  }
11208
+ const backlogTasks = task ? [task] : CANONICAL_DREAM_TASKS;
11209
+ const backlogBefore = getDreamTaskBacklogs(deps.db, project.projectIdentity, backlogTasks);
10699
11210
  sendCtxStatusMessage(pi, {
10700
11211
  title: "/ctx-dream",
10701
11212
  text: [
10702
11213
  "## /ctx-dream",
10703
11214
  "",
10704
11215
  task ? `Running dream task "${task}" for ${project.projectIdentity}…` : `Starting dream run for ${project.projectIdentity}…`,
10705
- `Project directory: ${project.projectDir}`
11216
+ `Project directory: ${project.projectDir}`,
11217
+ "",
11218
+ "Backlog before starting:",
11219
+ formatDreamTaskBacklogs(backlogBefore, backlogTasks)
10706
11220
  ].join(`
10707
11221
  `),
10708
11222
  level: "info"
@@ -10717,10 +11231,16 @@ Unknown task "${requested}".`,
10717
11231
  lines.push(`Ran: ${result.ran.join(", ")}`);
10718
11232
  if (result.failed.length > 0)
10719
11233
  lines.push(`Failed: ${result.failed.join(", ")}`);
11234
+ if ((result.failureDetails?.length ?? 0) > 0) {
11235
+ lines.push("Failure details:", ...(result.failureDetails ?? []).map((detail) => `- ${detail}`));
11236
+ }
10720
11237
  if (result.skippedNoWork.length > 0)
10721
11238
  lines.push(`Skipped (no work): ${result.skippedNoWork.join(", ")}`);
10722
11239
  if (result.deferredBusy.length > 0)
10723
11240
  lines.push(`Busy: ${result.deferredBusy.join(", ")} — another dream task holds this domain's lease; retry in a minute`);
11241
+ if (Object.keys(result.backlogAfter ?? {}).length > 0) {
11242
+ lines.push("", "Backlog at run end:", formatDreamTaskBacklogs(result.backlogAfter));
11243
+ }
10724
11244
  if (lines.length === 0)
10725
11245
  lines.push("No enabled dream tasks to run.");
10726
11246
  sendCtxStatusMessage(pi, {
@@ -11047,6 +11567,101 @@ function executeFlush(db, sessionId) {
11047
11567
  }
11048
11568
  }
11049
11569
 
11570
+ // src/compaction-off-pi.ts
11571
+ var COMPACTION_OFF_COMMAND_UNAVAILABLE = `Unavailable: magic-context is in compaction-off mode (${COMPACTION_ENABLED_PATH}=false).`;
11572
+ var NO_TRANSITION = {
11573
+ recordToWrite: null,
11574
+ invalidatedBaseline: false,
11575
+ clearDeferredMarkerState: false,
11576
+ historianCatchUpSignaled: false,
11577
+ notice: null
11578
+ };
11579
+ 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.";
11580
+ var PI_COMPACTION_ON_NOTICE = "Magic Context context-window management resumed. Run /ctx-wrapup to digest history accumulated while compaction was off.";
11581
+ function reconcilePiCompactionMode(args) {
11582
+ const stored = getCompactionModeRecord(args.db, args.sessionId);
11583
+ if (stored === "on_notice_pending") {
11584
+ return {
11585
+ ...NO_TRANSITION,
11586
+ recordToWrite: "on",
11587
+ notice: PI_COMPACTION_ON_NOTICE
11588
+ };
11589
+ }
11590
+ if (stored === "off_notice_pending") {
11591
+ return {
11592
+ ...NO_TRANSITION,
11593
+ recordToWrite: "off",
11594
+ notice: PI_COMPACTION_OFF_NOTICE
11595
+ };
11596
+ }
11597
+ if (!args.compactionOff) {
11598
+ if (stored === null || resolveCompactionModeRecord(stored) === "on") {
11599
+ return stored === null ? { ...NO_TRANSITION, recordToWrite: "on" } : NO_TRANSITION;
11600
+ }
11601
+ clearCachedM0M1(args.db, args.sessionId);
11602
+ if (!args.historianRunnable) {
11603
+ return {
11604
+ ...NO_TRANSITION,
11605
+ recordToWrite: "on",
11606
+ invalidatedBaseline: true
11607
+ };
11608
+ }
11609
+ updateSessionMeta(args.db, args.sessionId, {
11610
+ compartmentInProgress: true
11611
+ });
11612
+ setCompactionModeRecord(args.db, args.sessionId, "on_notice_pending");
11613
+ return {
11614
+ recordToWrite: "on",
11615
+ invalidatedBaseline: true,
11616
+ clearDeferredMarkerState: false,
11617
+ historianCatchUpSignaled: true,
11618
+ notice: PI_COMPACTION_ON_NOTICE
11619
+ };
11620
+ }
11621
+ if (resolveCompactionModeRecord(stored) === "off")
11622
+ return NO_TRANSITION;
11623
+ let clearedSomething = false;
11624
+ if (getPendingPiCompactionMarkerState(args.db, args.sessionId) !== null) {
11625
+ setPendingPiCompactionMarkerState(args.db, args.sessionId, null);
11626
+ clearedSomething = true;
11627
+ }
11628
+ if (getOverflowState(args.db, args.sessionId).needsEmergencyRecovery) {
11629
+ clearEmergencyRecovery(args.db, args.sessionId);
11630
+ clearedSomething = true;
11631
+ }
11632
+ const channel2 = getChannel2NudgeState(args.db, args.sessionId);
11633
+ if (channel2 === "pending" || channel2 === "claimed") {
11634
+ setChannel2NudgeState(args.db, args.sessionId, "");
11635
+ clearedSomething = true;
11636
+ }
11637
+ if (getPendingOps(args.db, args.sessionId).length > 0) {
11638
+ clearPendingOps(args.db, args.sessionId);
11639
+ clearedSomething = true;
11640
+ }
11641
+ const meta = getOrCreateSessionMeta(args.db, args.sessionId);
11642
+ if (meta.compartmentInProgress) {
11643
+ updateSessionMeta(args.db, args.sessionId, {
11644
+ compartmentInProgress: false
11645
+ });
11646
+ }
11647
+ clearCachedM0M1(args.db, args.sessionId);
11648
+ sessionLog(args.sessionId, `Pi compaction-off transition: clearedPendingMarker=${clearedSomething}`);
11649
+ const notice = clearedSomething ? PI_COMPACTION_OFF_NOTICE : null;
11650
+ if (notice) {
11651
+ setCompactionModeRecord(args.db, args.sessionId, "off_notice_pending");
11652
+ }
11653
+ return {
11654
+ recordToWrite: "off",
11655
+ invalidatedBaseline: true,
11656
+ clearDeferredMarkerState: true,
11657
+ historianCatchUpSignaled: false,
11658
+ notice
11659
+ };
11660
+ }
11661
+ function commitPiCompactionModeRecord(db, sessionId, record) {
11662
+ setCompactionModeRecord(db, sessionId, record);
11663
+ }
11664
+
11050
11665
  // src/context-handler.ts
11051
11666
  import * as crypto3 from "node:crypto";
11052
11667
 
@@ -11060,6 +11675,9 @@ var UNIT_TO_MS = {
11060
11675
  };
11061
11676
  function parseCacheTtl(ttl) {
11062
11677
  const normalizedTtl = ttl.trim();
11678
+ if (normalizedTtl.toLowerCase() === "never") {
11679
+ return Number.POSITIVE_INFINITY;
11680
+ }
11063
11681
  if (NUMERIC_PATTERN.test(normalizedTtl)) {
11064
11682
  return Number(normalizedTtl);
11065
11683
  }
@@ -11775,9 +12393,9 @@ function applyFlushedStatuses(sessionId, db, targets, preloadedTags) {
11775
12393
  }
11776
12394
 
11777
12395
  // ../plugin/src/hooks/magic-context/boundary-execution.ts
11778
- var FORCE_MATERIALIZE_PERCENTAGE = 85;
11779
12396
  function detectMidTurnBypassReason(input) {
11780
- if (input.contextUsage.percentage >= FORCE_MATERIALIZE_PERCENTAGE)
12397
+ const { forceMaterializationPercentage } = escalationBands(input.effectiveExecuteThresholdPercentage);
12398
+ if (input.contextUsage.percentage >= forceMaterializationPercentage)
11781
12399
  return "force-materialize";
11782
12400
  if (input.historyRefreshSessions.has(input.sessionId))
11783
12401
  return "explicit-bust";
@@ -12270,29 +12888,29 @@ function callIdFromPart(part) {
12270
12888
  const direct = firstStringField(part, ["callID", "callId", "toolCallId", "tool_call_id", "id"]);
12271
12889
  if (direct)
12272
12890
  return direct;
12273
- const state = isRecord(part.state) ? part.state : null;
12274
- return state ? firstStringField(state, ["callID", "callId", "toolCallId", "tool_call_id", "id"]) ?? "" : "";
12891
+ const state2 = isRecord(part.state) ? part.state : null;
12892
+ return state2 ? firstStringField(state2, ["callID", "callId", "toolCallId", "tool_call_id", "id"]) ?? "" : "";
12275
12893
  }
12276
12894
  function toolSignalFromPart(part) {
12277
12895
  if (!isRecord(part))
12278
12896
  return null;
12279
12897
  const type = partType(part);
12280
- const state = isRecord(part.state) ? part.state : null;
12898
+ const state2 = isRecord(part.state) ? part.state : null;
12281
12899
  const callId = callIdFromPart(part);
12282
12900
  if (!callId && type !== "tool")
12283
12901
  return null;
12284
12902
  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;
12903
+ const hasInput = state2 !== null && hasOwn(state2, "input");
12904
+ const outputKey = state2 ? hasOwn(state2, "output") ? "output" : hasOwn(state2, "error") ? "error" : hasOwn(state2, "result") ? "result" : null : null;
12287
12905
  const hasOutput = outputKey !== null;
12288
- const outputValue = outputKey && state ? state[outputKey] : undefined;
12906
+ const outputValue = outputKey && state2 ? state2[outputKey] : undefined;
12289
12907
  const providerExecuted = part.providerExecuted === true;
12290
12908
  const openInvocation = !providerExecuted && !hasOutput;
12291
12909
  return {
12292
12910
  callId,
12293
12911
  hasInput: hasInput || openInvocation,
12294
12912
  hasOutput,
12295
- inputText: hasInput && state ? stringValue(state.input) : "",
12913
+ inputText: hasInput && state2 ? stringValue(state2.input) : "",
12296
12914
  outputText: hasOutput ? stringValue(outputValue) : ""
12297
12915
  };
12298
12916
  }
@@ -12520,24 +13138,27 @@ function tokenForMessage(message, options) {
12520
13138
  function buildTrueRawTokenIndex(sessionId, messages, options) {
12521
13139
  const ordered = [...messages].sort((a, b) => a.ordinal - b.ordinal);
12522
13140
  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);
13141
+ const firstOrdinal = ordered.length > 0 ? ordered[0].ordinal : 1;
13142
+ const terminalOrdinal = ordered.length > 0 ? ordered[ordered.length - 1].ordinal : 0;
13143
+ const rawMessageCount = Math.max(sliceCount, terminalOrdinal, options.absoluteMessageCount ?? sliceCount);
13144
+ const ordinalSpan = terminalOrdinal >= firstOrdinal ? terminalOrdinal - firstOrdinal + 1 : 0;
12525
13145
  const tokensByOrdinal = new Map;
12526
13146
  const idsByOrdinal = new Map;
12527
- const prefix = new Array(rawMessageCount + 1).fill(0);
13147
+ const prefix = new Array(ordinalSpan + 1).fill(0);
12528
13148
  for (const message of ordered) {
12529
13149
  const stored = options.storedTotalForMessage?.(message);
12530
13150
  const total = stored !== undefined && stored !== null ? stored : tokenForMessage(message, options).total;
12531
13151
  tokensByOrdinal.set(message.ordinal, total);
12532
13152
  idsByOrdinal.set(message.ordinal, message.id);
12533
- if (message.ordinal >= 1 && message.ordinal <= rawMessageCount) {
12534
- prefix[message.ordinal] = total;
13153
+ const relative = message.ordinal - firstOrdinal + 1;
13154
+ if (relative >= 1 && relative <= ordinalSpan) {
13155
+ prefix[relative] = total;
12535
13156
  }
12536
13157
  }
12537
- for (let k = 1;k <= rawMessageCount; k += 1) {
13158
+ for (let k = 1;k <= ordinalSpan; k += 1) {
12538
13159
  prefix[k] += prefix[k - 1];
12539
13160
  }
12540
- const ordinalToIndex = (ordinal) => Math.max(0, Math.min(rawMessageCount, ordinal - 1));
13161
+ const ordinalToIndex = (ordinal) => Math.max(0, Math.min(ordinalSpan, ordinal - firstOrdinal));
12541
13162
  return {
12542
13163
  sessionId,
12543
13164
  providerShapeVersion: options.providerShapeVersion,
@@ -12549,27 +13170,27 @@ function buildTrueRawTokenIndex(sessionId, messages, options) {
12549
13170
  return idsByOrdinal.get(ordinal) ?? null;
12550
13171
  },
12551
13172
  suffixTokensFromOrdinal(ordinal) {
12552
- if (ordinal <= 1)
12553
- return prefix[rawMessageCount];
12554
- if (ordinal > rawMessageCount)
13173
+ if (ordinal <= firstOrdinal)
13174
+ return prefix[ordinalSpan];
13175
+ if (ordinal > terminalOrdinal)
12555
13176
  return 0;
12556
- return prefix[rawMessageCount] - prefix[ordinalToIndex(ordinal)];
13177
+ return prefix[ordinalSpan] - prefix[ordinalToIndex(ordinal)];
12557
13178
  },
12558
13179
  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];
13180
+ const start = Math.max(firstOrdinal, startInclusive);
13181
+ const end = Math.max(start, Math.min(terminalOrdinal + 1, endExclusive));
13182
+ return prefix[end - firstOrdinal] - prefix[start - firstOrdinal];
12562
13183
  },
12563
13184
  findSuffixStartForTokens(tokens) {
12564
13185
  if (!Number.isFinite(tokens) || tokens <= 0)
12565
- return rawMessageCount + 1;
13186
+ return terminalOrdinal + 1;
12566
13187
  const target = Math.max(0, Math.floor(tokens));
12567
- const total = prefix[rawMessageCount];
13188
+ const total = prefix[ordinalSpan];
12568
13189
  if (total < target)
12569
- return 1;
13190
+ return firstOrdinal;
12570
13191
  const cut = total - target;
12571
13192
  let lo = 0;
12572
- let hi = rawMessageCount;
13193
+ let hi = ordinalSpan;
12573
13194
  let best = 0;
12574
13195
  while (lo <= hi) {
12575
13196
  const mid = lo + hi >> 1;
@@ -12580,29 +13201,31 @@ function buildTrueRawTokenIndex(sessionId, messages, options) {
12580
13201
  hi = mid - 1;
12581
13202
  }
12582
13203
  }
12583
- return best + 1;
13204
+ return firstOrdinal + best;
12584
13205
  },
12585
13206
  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));
13207
+ const start = Math.max(firstOrdinal, Math.min(terminalOrdinal + 1, startInclusive));
13208
+ const end = Math.max(start, Math.min(terminalOrdinal + 1, endExclusive));
12588
13209
  if (!Number.isFinite(capTokens) || capTokens <= 0)
12589
13210
  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;
13211
+ const startIndex = start - firstOrdinal;
13212
+ const endIndex = end - firstOrdinal;
13213
+ const cut = prefix[startIndex] + Math.floor(capTokens);
13214
+ let lo = startIndex + 1;
13215
+ let hi = endIndex;
13216
+ let bestEndIndex = startIndex;
12595
13217
  while (lo <= hi) {
12596
13218
  const mid = lo + hi >> 1;
12597
13219
  if (prefix[mid] <= cut) {
12598
- bestEnd = mid + 1;
13220
+ bestEndIndex = mid;
12599
13221
  lo = mid + 1;
12600
13222
  } else {
12601
13223
  hi = mid - 1;
12602
13224
  }
12603
13225
  }
13226
+ let bestEnd = firstOrdinal + bestEndIndex;
12604
13227
  if (bestEnd === start && start < end)
12605
- return start + 1;
13228
+ bestEnd = start + 1;
12606
13229
  return Math.min(bestEnd, end);
12607
13230
  }
12608
13231
  };
@@ -12918,7 +13541,8 @@ function resolveProtectedTailBoundary(ctx) {
12918
13541
  if (ctx.migrationFloorActive)
12919
13542
  runtimeFloor = Math.max(runtimeFloor, ctx.priorBoundaryOrdinal);
12920
13543
  let protectedTailStart = Math.max(boundary, runtimeFloor);
12921
- if (!ctx.emergencyTailScale && usagePercentage < 80) {
13544
+ const forceMaterializationPercentage = escalationBands(ctx.executeThresholdPercentage).forceMaterializationPercentage;
13545
+ if (!ctx.emergencyTailScale && usagePercentage < forceMaterializationPercentage) {
12922
13546
  let lastMeaningfulUserOrdinal = 0;
12923
13547
  for (let i = messages.length - 1;i >= 0; i--) {
12924
13548
  const message = messages[i];
@@ -13159,7 +13783,8 @@ function getRawHistoryEligibility(db, sessionId) {
13159
13783
  function hasRunnableCompartmentWindow(snapshot) {
13160
13784
  if (snapshot.offset >= snapshot.protectedTailStart)
13161
13785
  return false;
13162
- if (snapshot.usagePercentage >= 80 || snapshot.emergencyTailScale) {
13786
+ const forceMaterializationPercentage = escalationBands(snapshot.executeThresholdPercentage).forceMaterializationPercentage;
13787
+ if (snapshot.usagePercentage >= forceMaterializationPercentage || snapshot.emergencyTailScale) {
13163
13788
  return snapshot.trueRawEligibleTokens >= deriveMinForceEligibleTokens(snapshot.N) || snapshot.eligibleEndOrdinal > snapshot.offset;
13164
13789
  }
13165
13790
  return snapshot.eligibleEndOrdinal > snapshot.offset;
@@ -13222,11 +13847,18 @@ function validateBoundarySnapshot(args) {
13222
13847
  return { ok: true };
13223
13848
  }
13224
13849
  function recordHighPressureNoEligibleHead(db, snapshot) {
13225
- if (snapshot.usagePercentage < 80 && !snapshot.emergencyTailScale)
13850
+ const forceMaterializationPercentage = escalationBands(snapshot.executeThresholdPercentage).forceMaterializationPercentage;
13851
+ if (snapshot.usagePercentage < forceMaterializationPercentage && !snapshot.emergencyTailScale) {
13226
13852
  return 0;
13853
+ }
13227
13854
  return recordProtectedTailNoEligibleHead(db, snapshot.sessionId);
13228
13855
  }
13229
13856
 
13857
+ // ../plugin/src/hooks/magic-context/sentinel.ts
13858
+ function modelAcceptsEmptyContent(providerID) {
13859
+ return providerID === "anthropic";
13860
+ }
13861
+
13230
13862
  // ../plugin/src/hooks/magic-context/compartment-trigger.ts
13231
13863
  var PROACTIVE_TRIGGER_OFFSET_PERCENTAGE = 2;
13232
13864
  var POST_DROP_TARGET_RATIO = 0.75;
@@ -13234,7 +13866,6 @@ var MIN_PROACTIVE_TAIL_TOKEN_ESTIMATE = 6000;
13234
13866
  var MIN_PROACTIVE_TAIL_MESSAGE_COUNT = 12;
13235
13867
  var DEFAULT_MIN_COMMIT_CLUSTERS_FOR_TRIGGER = 3;
13236
13868
  var TAIL_SIZE_TRIGGER_MULTIPLIER = 3;
13237
- var FORCE_COMPARTMENT_PERCENTAGE = 80;
13238
13869
  var BLOCK_UNTIL_DONE_PERCENTAGE = 95;
13239
13870
  var CONTENT_TAG_OWNER_SUFFIX = /:(?:p|file)\d+$/;
13240
13871
  function tagOwnerMessageId(row) {
@@ -13271,7 +13902,7 @@ function estimateUntaggedInMemoryTailUpperBound(db, sessionId, inMemoryTail, tag
13271
13902
  function getProactiveCompartmentTriggerPercentage(executeThresholdPercentage) {
13272
13903
  return Math.max(0, executeThresholdPercentage - PROACTIVE_TRIGGER_OFFSET_PERCENTAGE);
13273
13904
  }
13274
- function estimateProjectedPostDropPercentage(db, sessionId, usage, activeTags, clearReasoningAge, clearedReasoningThroughTag) {
13905
+ function estimateProjectedPostDropPercentage(db, sessionId, usage, activeTags, clearReasoningAge, clearedReasoningThroughTag, canClearReasoning) {
13275
13906
  const totalActiveBytes = activeTags.reduce((sum, tag) => sum + tag.byteSize + tag.reasoningByteSize, 0);
13276
13907
  if (totalActiveBytes === 0)
13277
13908
  return null;
@@ -13282,7 +13913,7 @@ function estimateProjectedPostDropPercentage(db, sessionId, usage, activeTags, c
13282
13913
  droppableBytes += activeTags.filter((tag) => pendingDropTagIds.has(tag.tagNumber)).reduce((sum, tag) => sum + tag.byteSize + tag.reasoningByteSize, 0);
13283
13914
  }
13284
13915
  const maxTag = activeTags.reduce((max, t) => Math.max(max, t.tagNumber), 0);
13285
- if (clearReasoningAge !== undefined && clearedReasoningThroughTag !== undefined) {
13916
+ if (canClearReasoning && clearReasoningAge !== undefined && clearedReasoningThroughTag !== undefined) {
13286
13917
  const reasoningAgeCutoff = maxTag - clearReasoningAge;
13287
13918
  for (const tag of activeTags) {
13288
13919
  if (tag.type !== "message")
@@ -13382,7 +14013,7 @@ function getUnsummarizedTailInfo(db, sessionId, triggerBudget, usage, executeThr
13382
14013
  }
13383
14014
  });
13384
14015
  }
13385
- function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPercentage, executeThresholdPercentage, triggerBudget, clearReasoningAge, commitClusterTrigger, preloadedActiveTags, contextLimit, inMemoryTail, taggerFloorOverride) {
14016
+ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPercentage, executeThresholdPercentage, triggerBudget, clearReasoningAge, commitClusterTrigger, preloadedActiveTags, contextLimit, inMemoryTail, taggerFloorOverride, reasoningProjection) {
13386
14017
  if (sessionMeta.compartmentInProgress) {
13387
14018
  sessionLog(sessionId, `compartment trigger: skipped — historian already in progress (usage=${usage.percentage.toFixed(1)}%)`);
13388
14019
  return { shouldFire: false };
@@ -13436,18 +14067,20 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
13436
14067
  }
13437
14068
  return { shouldFire: false };
13438
14069
  }
13439
- const projectedPostDropPercentage = estimateProjectedPostDropPercentage(db, sessionId, usage, preloadedActiveTags ?? getActiveTagsBySession(db, sessionId), clearReasoningAge, sessionMeta.clearedReasoningThroughTag);
14070
+ const canClearReasoning = reasoningProjection?.canClearReasoning ?? modelAcceptsEmptyContent(reasoningProjection?.providerID);
14071
+ const projectedPostDropPercentage = estimateProjectedPostDropPercentage(db, sessionId, usage, preloadedActiveTags ?? getActiveTagsBySession(db, sessionId), clearReasoningAge, sessionMeta.clearedReasoningThroughTag, canClearReasoning);
13440
14072
  const relativePostDropTarget = executeThresholdPercentage * POST_DROP_TARGET_RATIO;
13441
- if (usage.percentage >= FORCE_COMPARTMENT_PERCENTAGE) {
14073
+ const forceMaterializationPercentage = escalationBands(executeThresholdPercentage).forceMaterializationPercentage;
14074
+ if (usage.percentage >= forceMaterializationPercentage) {
13442
14075
  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)}%)`);
14076
+ sessionLog(sessionId, `compartment trigger: skipping force band ${forceMaterializationPercentage}% because projected post-drop usage is ${projectedPostDropPercentage.toFixed(1)}% (target ${relativePostDropTarget.toFixed(1)}%)`);
13444
14077
  return { shouldFire: false };
13445
14078
  }
13446
14079
  sessionLog(sessionId, `compartment trigger: force-firing at ${usage.percentage.toFixed(1)}% (projected post-drop ${projectedPostDropPercentage?.toFixed(1) ?? "none"}%)`);
13447
14080
  if (tailInfo.boundarySnapshot && hasRunnableCompartmentWindow(tailInfo.boundarySnapshot)) {
13448
14081
  return {
13449
14082
  shouldFire: true,
13450
- reason: "force_80",
14083
+ reason: "force_band",
13451
14084
  boundarySnapshot: tailInfo.boundarySnapshot
13452
14085
  };
13453
14086
  }
@@ -13472,9 +14105,9 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
13472
14105
  });
13473
14106
  });
13474
14107
  if (hasRunnableCompartmentWindow(scaledBoundary)) {
13475
- return { shouldFire: true, reason: "force_80", boundarySnapshot: scaledBoundary };
14108
+ return { shouldFire: true, reason: "force_band", boundarySnapshot: scaledBoundary };
13476
14109
  }
13477
- sessionLog(sessionId, "compartment trigger: force_80 skipped — raw exists but protected head genuinely empty after emergency tail scale");
14110
+ sessionLog(sessionId, "compartment trigger: force_band skipped — raw exists but protected head genuinely empty after emergency tail scale");
13478
14111
  return { shouldFire: false };
13479
14112
  }
13480
14113
  const clusterEnabled = commitClusterTrigger?.enabled ?? true;
@@ -13639,11 +14272,6 @@ ${body}${hintText}
13639
14272
  </system-reminder>`;
13640
14273
  }
13641
14274
 
13642
- // ../plugin/src/hooks/magic-context/sentinel.ts
13643
- function modelAcceptsEmptyContent(providerID) {
13644
- return providerID === "anthropic";
13645
- }
13646
-
13647
14275
  // ../plugin/src/hooks/magic-context/supersession-reclaim.ts
13648
14276
  var TODOWRITE_KEEP = 1;
13649
14277
  var CTX_REDUCE_KEEP = 5;
@@ -14958,16 +15586,16 @@ function sealDeliveredAfterUnconfirmedSend(db, sessionId) {
14958
15586
  }
14959
15587
  }
14960
15588
  var channel1StateBySession = new Map;
14961
- function setPiChannel1Baseline(sessionId, state) {
14962
- channel1StateBySession.set(sessionId, state);
15589
+ function setPiChannel1Baseline(sessionId, state2) {
15590
+ channel1StateBySession.set(sessionId, state2);
14963
15591
  }
14964
15592
  function clearPiChannel1State(sessionId) {
14965
15593
  channel1StateBySession.delete(sessionId);
14966
15594
  }
14967
15595
  function markPiChannel1Reduced(sessionId, db) {
14968
- const state = channel1StateBySession.get(sessionId);
14969
- if (state)
14970
- state.reducedSinceRefresh = true;
15596
+ const state2 = channel1StateBySession.get(sessionId);
15597
+ if (state2)
15598
+ state2.reducedSinceRefresh = true;
14971
15599
  if (db)
14972
15600
  resetLastNudgeCycle(db, sessionId);
14973
15601
  }
@@ -15042,11 +15670,11 @@ function computeTailTokenEstimatePi(messages) {
15042
15670
  }
15043
15671
  function maybeChannel1ReminderForToolResult(args) {
15044
15672
  const { db, sessionId, toolName } = args;
15045
- const state = channel1StateBySession.get(sessionId);
15046
- if (!state)
15673
+ const state2 = channel1StateBySession.get(sessionId);
15674
+ if (!state2)
15047
15675
  return null;
15048
15676
  if (toolName === "ctx_reduce") {
15049
- state.reducedSinceRefresh = true;
15677
+ state2.reducedSinceRefresh = true;
15050
15678
  resetLastNudgeCycle(db, sessionId);
15051
15679
  return null;
15052
15680
  }
@@ -15055,21 +15683,21 @@ function maybeChannel1ReminderForToolResult(args) {
15055
15683
  return null;
15056
15684
  if (text.includes(CHANNEL1_SENTINEL))
15057
15685
  return null;
15058
- state.turnToolTokens += toolOutputTokens(text);
15059
- if (state.reducedSinceRefresh)
15686
+ state2.turnToolTokens += toolOutputTokens(text);
15687
+ if (state2.reducedSinceRefresh)
15060
15688
  return null;
15061
- const undroppedTokens = state.tailToolTokens + state.turnToolTokens;
15689
+ const undroppedTokens = state2.tailToolTokens + state2.turnToolTokens;
15062
15690
  const pressure = computePressure({
15063
- lastInputTokens: state.lastInputTokens,
15064
- turnToolTokens: state.turnToolTokens,
15065
- contextLimit: state.contextLimit,
15066
- executeThresholdPercentage: state.executeThresholdPercentage
15691
+ lastInputTokens: state2.lastInputTokens,
15692
+ turnToolTokens: state2.turnToolTokens,
15693
+ contextLimit: state2.contextLimit,
15694
+ executeThresholdPercentage: state2.executeThresholdPercentage
15067
15695
  });
15068
- const workingWindowTokens = Math.round(state.contextLimit * state.executeThresholdPercentage / 100);
15696
+ const workingWindowTokens = Math.round(state2.contextLimit * state2.executeThresholdPercentage / 100);
15069
15697
  const decision = decideChannel1({
15070
15698
  undroppedTokens,
15071
15699
  pressure,
15072
- estimatedInputTokens: state.lastInputTokens + state.turnToolTokens,
15700
+ estimatedInputTokens: state2.lastInputTokens + state2.turnToolTokens,
15073
15701
  workingWindowTokens,
15074
15702
  lastNudgeUndropped: getLastNudgeUndropped(db, sessionId),
15075
15703
  lastNudgeLevel: getLastNudgeLevel(db, sessionId),
@@ -15081,18 +15709,18 @@ function maybeChannel1ReminderForToolResult(args) {
15081
15709
  return null;
15082
15710
  return {
15083
15711
  type: "text",
15084
- text: buildChannel1Reminder(decision.level, decision.undroppedTokens, state.oldestReclaimableToolTags)
15712
+ text: buildChannel1Reminder(decision.level, decision.undroppedTokens, state2.oldestReclaimableToolTags)
15085
15713
  };
15086
15714
  }
15087
15715
  var CHANNEL2_NUDGE_CUSTOM_TYPE = "magic-context:ceiling-nudge";
15088
15716
  function maybeDeliverChannel2Pi(pi, db, sessionId, deliverAs = "followUp") {
15089
- let state;
15717
+ let state2;
15090
15718
  try {
15091
- state = getChannel2NudgeState(db, sessionId);
15719
+ state2 = getChannel2NudgeState(db, sessionId);
15092
15720
  } catch {
15093
15721
  return false;
15094
15722
  }
15095
- if (state !== "pending")
15723
+ if (state2 !== "pending")
15096
15724
  return false;
15097
15725
  const baseline = channel1StateBySession.get(sessionId);
15098
15726
  if (!baseline)
@@ -15764,14 +16392,17 @@ var EMPTY_PI_PROJECT_DOCS = {
15764
16392
  renderedBlock: "",
15765
16393
  canonicalHash: ""
15766
16394
  };
15767
- function readProjectDocsForPiM0(state) {
15768
- return state.injectDocs !== false ? readProjectDocsCanonical(state.projectDirectory) : EMPTY_PI_PROJECT_DOCS;
16395
+ function getRenderableCompartmentsPi(db, state2) {
16396
+ return state2.compactionOff ? [] : getCompartments(db, state2.sessionId);
16397
+ }
16398
+ function readProjectDocsForPiM0(state2) {
16399
+ return state2.injectDocs !== false ? readProjectDocsCanonical(state2.projectDirectory) : EMPTY_PI_PROJECT_DOCS;
15769
16400
  }
15770
- function memoryProjectPath(state) {
15771
- return state.memoryEnabled === false ? undefined : state.projectIdentity;
16401
+ function memoryProjectPath(state2) {
16402
+ return state2.memoryEnabled === false ? undefined : state2.projectIdentity;
15772
16403
  }
15773
- function resolveWorkspaceRenderContextPi(state, db) {
15774
- const memPath = memoryProjectPath(state);
16404
+ function resolveWorkspaceRenderContextPi(state2, db) {
16405
+ const memPath = memoryProjectPath(state2);
15775
16406
  if (!memPath) {
15776
16407
  return {
15777
16408
  identities: [],
@@ -15847,12 +16478,12 @@ function piImageFromDataUrl(dataUrl) {
15847
16478
  return null;
15848
16479
  return { type: "image", mimeType: match[1], data: match[2] };
15849
16480
  }
15850
- function resolveMuralForM0Pi(state, db, modelKey, budgetTokens) {
15851
- if (state.mural)
15852
- return state.mural;
15853
- if (!state.muralEnabled)
16481
+ function resolveMuralForM0Pi(state2, db, modelKey, budgetTokens) {
16482
+ if (state2.mural)
16483
+ return state2.mural;
16484
+ if (!state2.muralEnabled)
15854
16485
  return;
15855
- return resolveMuralWire(db, memoryProjectPath(state), modelKey, true, budgetTokens);
16486
+ return resolveMuralWire(db, memoryProjectPath(state2), modelKey, true, budgetTokens);
15856
16487
  }
15857
16488
  function cachedInjectionTokenCounts(sessionId, m0, m1) {
15858
16489
  const cached = injectionTokenCountsBySession.get(sessionId);
@@ -15905,17 +16536,17 @@ function trimPiMessagesToCachedBoundary(db, sessionId, piMessages, entryIds) {
15905
16536
  function setCachedBoundary(db, sessionId, boundary) {
15906
16537
  db.prepare("UPDATE session_meta SET cached_m0_last_baseline_end_message_id = ? WHERE session_id = ?").run(boundary, sessionId);
15907
16538
  }
15908
- function getCachedMarkers(db, state, compartmentsForNormalization) {
15909
- const meta = getOrCreateSessionMeta(db, state.sessionId);
16539
+ function getCachedMarkers(db, state2, compartmentsForNormalization) {
16540
+ const meta = getOrCreateSessionMeta(db, state2.sessionId);
15910
16541
  if (!meta.cachedM0Bytes)
15911
16542
  return null;
15912
16543
  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
16544
  return null;
15914
16545
  }
15915
- const compartments = compartmentsForNormalization ?? getCompartments(db, state.sessionId);
16546
+ const compartments = compartmentsForNormalization ?? getRenderableCompartmentsPi(db, state2);
15916
16547
  const cachedUpgradeIdentity = decodeCachedM0UpgradeIdentity(meta.cachedM0UpgradeState);
15917
16548
  const maxCompartmentSeq = normalizeCachedMaxCompartmentSeq(meta.cachedM0MaxCompartmentSeq, compartments);
15918
- const cachedBoundary = getCachedBoundary(db, state.sessionId);
16549
+ const cachedBoundary = getCachedBoundary(db, state2.sessionId);
15919
16550
  const liveBoundary = lastBaselineEndMessageId(compartments);
15920
16551
  if (maxCompartmentSeq >= 0 && cachedBoundary === null && liveBoundary !== null) {
15921
16552
  return null;
@@ -15943,38 +16574,38 @@ function lastBaselineEndMessageId(compartments) {
15943
16574
  const last = compartments.at(-1);
15944
16575
  return last?.endMessageId && last.endMessageId.length > 0 ? last.endMessageId : null;
15945
16576
  }
15946
- function readCurrentMarkers(db, state, projectDocsHash) {
15947
- return readCurrentMarkersFromCompartments(db, state, getCompartments(db, state.sessionId), projectDocsHash);
16577
+ function readCurrentMarkers(db, state2, projectDocsHash) {
16578
+ return readCurrentMarkersFromCompartments(db, state2, getRenderableCompartmentsPi(db, state2), projectDocsHash);
15948
16579
  }
15949
- function readCurrentMarkersFromCompartments(db, state, compartments, projectDocsHash) {
15950
- const memPath = memoryProjectPath(state);
15951
- const workspace = resolveWorkspaceRenderContextPi(state, db);
16580
+ function readCurrentMarkersFromCompartments(db, state2, compartments, projectDocsHash) {
16581
+ const memPath = memoryProjectPath(state2);
16582
+ const workspace = resolveWorkspaceRenderContextPi(state2, db);
15952
16583
  const maxMemoryId = memPath ? workspace.isWorkspaced ? getMaxMemoryIdForProjects(db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryIdForProjects(db, [memPath]) : 0;
15953
16584
  const projectState = memPath ? getProjectState(db, memPath) : undefined;
15954
16585
  const globalState = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH);
15955
16586
  return {
15956
16587
  maxCompartmentSeq: compartments.length > 0 ? compartments.reduce((max, compartment) => compartment.sequence > max ? compartment.sequence : max, EMPTY_MAX_COMPARTMENT_SEQ) : EMPTY_MAX_COMPARTMENT_SEQ,
15957
16588
  maxMemoryId,
15958
- maxMutationId: getMaxM0MutationId(db, state.sessionId) ?? 0,
16589
+ maxMutationId: getMaxM0MutationId(db, state2.sessionId) ?? 0,
15959
16590
  maxMemoryMutationId: memPath ? workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(db, workspace.expandedIdentities) ?? 0 : getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
15960
16591
  projectMemoryEpoch: projectState?.projectMemoryEpoch ?? 0,
15961
16592
  workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(db, workspace.identities) : null,
15962
16593
  projectUserProfileVersion: globalState?.projectUserProfileVersion ?? 0,
15963
- projectDocsHash: projectDocsHash ?? readProjectDocsForPiM0(state).canonicalHash,
15964
- sessionFactsVersion: getSessionFactsVersion(db, state.sessionId),
16594
+ projectDocsHash: projectDocsHash ?? readProjectDocsForPiM0(state2).canonicalHash,
16595
+ sessionFactsVersion: getSessionFactsVersion(db, state2.sessionId),
15965
16596
  materializedAt: Date.now(),
15966
16597
  upgradeState: `${PI_M0_UPGRADE_STATE}:${compartments.some((c) => c.legacy === 1) ? "legacy" : "ready"}`,
15967
16598
  compartmentRenderEpoch: COMPARTMENT_RENDER_EPOCH,
15968
16599
  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
16600
+ systemHash: (state2.hardSignals ?? EMPTY_PI_HARD_SIGNALS).systemHash,
16601
+ modelKey: (state2.hardSignals ?? EMPTY_PI_HARD_SIGNALS).modelKey,
16602
+ projectIdentity: state2.projectIdentity
15972
16603
  };
15973
16604
  }
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);
16605
+ function mustMaterializePi(state2, db, currentCompartmentsOverride) {
16606
+ const meta = getOrCreateSessionMeta(db, state2.sessionId);
16607
+ const currentCompartments = currentCompartmentsOverride ?? getRenderableCompartmentsPi(db, state2);
16608
+ const current = readCurrentMarkersFromCompartments(db, state2, currentCompartments, meta.cachedM0ProjectDocsHash ?? undefined);
15978
16609
  if (!meta.cachedM0Bytes)
15979
16610
  return { value: true, reason: "first_render" };
15980
16611
  if (!meta.cachedM1Bytes)
@@ -15982,21 +16613,21 @@ function mustMaterializePi(state, db, currentCompartmentsOverride) {
15982
16613
  if (!decodeCachedM0(meta.cachedM0Bytes)) {
15983
16614
  return { value: true, reason: "cache_invalid" };
15984
16615
  }
15985
- const cached = getCachedMarkers(db, state, currentCompartments);
16616
+ const cached = getCachedMarkers(db, state2, currentCompartments);
15986
16617
  if (cached === null) {
15987
16618
  return { value: true, reason: "cache_invalid" };
15988
16619
  }
15989
16620
  if (cached.compartmentRenderEpoch !== current.compartmentRenderEpoch) {
15990
16621
  return { value: true, reason: "compartment_render_epoch" };
15991
16622
  }
15992
- const hard = state.hardSignals ?? EMPTY_PI_HARD_SIGNALS;
16623
+ const hard = state2.hardSignals ?? EMPTY_PI_HARD_SIGNALS;
15993
16624
  if (hard.modelKey !== "" && hard.modelKey !== (meta.cachedM0ModelKey ?? "")) {
15994
16625
  return { value: true, reason: "model_change" };
15995
16626
  }
15996
16627
  if (hard.systemHash !== "" && hard.systemHash !== (meta.cachedM0SystemHash ?? "")) {
15997
16628
  return { value: true, reason: "system_hash" };
15998
16629
  }
15999
- if (meta.cachedM0ProjectIdentity !== null && meta.cachedM0ProjectIdentity !== state.projectIdentity) {
16630
+ if (meta.cachedM0ProjectIdentity !== null && meta.cachedM0ProjectIdentity !== state2.projectIdentity) {
16000
16631
  return { value: true, reason: "project_change" };
16001
16632
  }
16002
16633
  if (hard.cacheExpired && hard.lastResponseTime > 0 && hard.lastResponseTime > (meta.cachedM0MaterializedAt ?? 0)) {
@@ -16026,9 +16657,9 @@ ${memories.map((memory) => `- ${escapeXmlContent(memory.content)}`).join(`
16026
16657
  `)}
16027
16658
  </${wrapper}>`;
16028
16659
  }
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);
16660
+ function renderM0Pi(state2, db, projectDocs = readProjectDocsForPiM0(state2).renderedBlock, decayPressureMultiplier = 1, memoriesOverride, compartmentsOverride, userProfileOverride, workspaceOverride, mural) {
16661
+ const memPath = memoryProjectPath(state2);
16662
+ const workspace = workspaceOverride ?? resolveWorkspaceRenderContextPi(state2, db);
16032
16663
  const allMemories = memoriesOverride ?? (memPath ? workspace.isWorkspaced ? getMemoriesByProjects(db, workspace.expandedIdentities, ["active", "permanent"], Date.now(), workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(db, memPath, ["active", "permanent"]) : []);
16033
16664
  const memoryRenderOptions = {
16034
16665
  sourceNameByMemoryId: sourceNamesForPiMemories({
@@ -16037,23 +16668,25 @@ function renderM0Pi(state, db, projectDocs = readProjectDocsForPiM0(state).rende
16037
16668
  workspace
16038
16669
  })
16039
16670
  };
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;
16671
+ 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
16672
  const memoryBlock = memories.length > 0 ? renderMemoryBlockV2(memories, "project-memory", memoryRenderOptions) : undefined;
16042
- const baseHistoryBudget = state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
16673
+ const baseHistoryBudget = state2.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
16043
16674
  const decayed = renderDecayedCompartments({
16044
- compartments: compartmentsOverride ?? getCompartments(db, state.sessionId),
16675
+ compartments: compartmentsOverride ?? getRenderableCompartmentsPi(db, state2),
16045
16676
  historyBudgetTokens: baseHistoryBudget / Math.max(1, decayPressureMultiplier)
16046
16677
  });
16047
16678
  const sections = [];
16048
16679
  if (projectDocs.length > 0)
16049
16680
  sections.push(projectDocs);
16050
- const trimmedProfile = trimUserMemoriesToBudget(userProfileOverride ?? safeGetActiveUserMemoriesPi(db), state.userProfileBudgetTokens ?? DEFAULT_USER_PROFILE_BUDGET_TOKENS);
16681
+ const trimmedProfile = trimUserMemoriesToBudget(userProfileOverride ?? safeGetActiveUserMemoriesPi(db), state2.userProfileBudgetTokens ?? DEFAULT_USER_PROFILE_BUDGET_TOKENS);
16051
16682
  const userProfile = renderUserProfileBlock(db, "user-profile", trimmedProfile);
16052
16683
  if (userProfile.length > 0)
16053
16684
  sections.push(userProfile);
16054
- sections.push(decayed.length > 0 ? `<session-history>
16685
+ if (!state2.compactionOff) {
16686
+ sections.push(decayed.length > 0 ? `<session-history>
16055
16687
  ${decayed}
16056
16688
  </session-history>` : "<session-history></session-history>");
16689
+ }
16057
16690
  if (memoryBlock)
16058
16691
  sections.push(memoryBlock);
16059
16692
  if (mural?.enabled && mural.supportsVision && mural.dataUrl) {
@@ -16065,18 +16698,18 @@ The project memory mural image follows.
16065
16698
 
16066
16699
  `).trim();
16067
16700
  }
16068
- function renderedMemoryIdsForPi(state, memories, workspace, db) {
16701
+ function renderedMemoryIdsForPi(state2, memories, workspace, db) {
16069
16702
  if (memories.length === 0)
16070
16703
  return [];
16071
- const resolvedWorkspace = workspace ?? (db ? resolveWorkspaceRenderContextPi(state, db) : undefined);
16704
+ const resolvedWorkspace = workspace ?? (db ? resolveWorkspaceRenderContextPi(state2, db) : undefined);
16072
16705
  const renderOptions = resolvedWorkspace ? {
16073
16706
  sourceNameByMemoryId: sourceNamesForPiMemories({
16074
16707
  memories,
16075
- projectPath: memoryProjectPath(state),
16708
+ projectPath: memoryProjectPath(state2),
16076
16709
  workspace: resolvedWorkspace
16077
16710
  })
16078
16711
  } : {};
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);
16712
+ 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
16713
  return trimmed.renderOrder.map((memory) => memory.id);
16081
16714
  }
16082
16715
  function isTransientSqliteLockError(error) {
@@ -16099,11 +16732,11 @@ class PiMaterializeContentionError extends Error {
16099
16732
  this.name = "PiMaterializeContentionError";
16100
16733
  }
16101
16734
  }
16102
- function readFrozenM0InputsPi(state, db, docs = readProjectDocsForPiM0(state), memoryCutoff) {
16103
- const memPath = memoryProjectPath(state);
16735
+ function readFrozenM0InputsPi(state2, db, docs = readProjectDocsForPiM0(state2), memoryCutoff) {
16736
+ const memPath = memoryProjectPath(state2);
16104
16737
  const read = db.transaction(() => {
16105
- const workspace = resolveWorkspaceRenderContextPi(state, db);
16106
- const compartments = getCompartments(db, state.sessionId);
16738
+ const workspace = resolveWorkspaceRenderContextPi(state2, db);
16739
+ const compartments = getRenderableCompartmentsPi(db, state2);
16107
16740
  const memories = memPath ? workspace.isWorkspaced ? getMemoriesByProjects(db, workspace.expandedIdentities, ["active", "permanent"], memoryCutoff, workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject(db, memPath, ["active", "permanent"], memoryCutoff) : [];
16108
16741
  const userProfile = safeGetActiveUserMemoriesPi(db);
16109
16742
  const projectState = memPath ? getProjectState(db, memPath) : undefined;
@@ -16111,72 +16744,72 @@ function readFrozenM0InputsPi(state, db, docs = readProjectDocsForPiM0(state), m
16111
16744
  const markers = {
16112
16745
  maxCompartmentSeq: compartments.reduce((max, compartment) => compartment.sequence > max ? compartment.sequence : max, EMPTY_MAX_COMPARTMENT_SEQ),
16113
16746
  maxMemoryId: memPath ? workspace.isWorkspaced ? getMaxMemoryIdForProjects(db, workspace.expandedIdentities, workspace.ownIdentities, workspace.shareCategories) : getMaxMemoryIdForProjects(db, [memPath]) : 0,
16114
- maxMutationId: getMaxM0MutationId(db, state.sessionId) ?? 0,
16747
+ maxMutationId: getMaxM0MutationId(db, state2.sessionId) ?? 0,
16115
16748
  maxMemoryMutationId: memPath ? workspace.isWorkspaced ? getMaxMemoryMutationIdForProjects(db, workspace.expandedIdentities) ?? 0 : getMaxMemoryMutationId(db, memPath) ?? 0 : 0,
16116
16749
  projectMemoryEpoch: projectState?.projectMemoryEpoch ?? 0,
16117
16750
  workspaceFingerprint: workspace.isWorkspaced ? computeWorkspaceEpochFingerprint(db, workspace.identities) : null,
16118
16751
  projectUserProfileVersion: globalState?.projectUserProfileVersion ?? 0,
16119
16752
  projectDocsHash: docs.canonicalHash,
16120
- sessionFactsVersion: getSessionFactsVersion(db, state.sessionId),
16753
+ sessionFactsVersion: getSessionFactsVersion(db, state2.sessionId),
16121
16754
  materializedAt: memoryCutoff ?? Date.now(),
16122
16755
  upgradeState: `${PI_M0_UPGRADE_STATE}:${compartments.some((c) => c.legacy === 1) ? "legacy" : "ready"}`,
16123
16756
  compartmentRenderEpoch: COMPARTMENT_RENDER_EPOCH,
16124
16757
  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
16758
+ systemHash: (state2.hardSignals ?? EMPTY_PI_HARD_SIGNALS).systemHash,
16759
+ modelKey: (state2.hardSignals ?? EMPTY_PI_HARD_SIGNALS).modelKey,
16760
+ projectIdentity: state2.projectIdentity
16128
16761
  };
16129
16762
  return { docs, markers, compartments, memories, userProfile, workspace };
16130
16763
  });
16131
16764
  return read();
16132
16765
  }
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);
16766
+ function renderFreshM0PiNonPersisted(state2, db) {
16767
+ const docs = readProjectDocsForPiM0(state2);
16768
+ const cachedMaterializedAt = getOrCreateSessionMeta(db, state2.sessionId).cachedM0MaterializedAt ?? 0;
16769
+ const frozen = readFrozenM0InputsPi(state2, db, docs, cachedMaterializedAt);
16137
16770
  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);
16771
+ const historyBudget = state2.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
16772
+ const memoryBudget = state2.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
16773
+ const mural = resolveMuralForM0Pi(state2, db, frozen.markers.modelKey, memoryBudget);
16774
+ rememberPiMural(state2.sessionId, mural);
16142
16775
  let dpm = 1;
16143
- let m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile, frozen.workspace, mural);
16776
+ let m0 = renderM0Pi(state2, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile, frozen.workspace, mural);
16144
16777
  let attempts = 0;
16145
16778
  while (historyBudget > 0 && historySliceTokensPi(m0) > historyBudget * 1.05 && attempts < 3) {
16146
16779
  dpm *= 1.15;
16147
- m0 = renderM0Pi(state, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile, frozen.workspace, mural);
16780
+ m0 = renderM0Pi(state2, db, docs.renderedBlock, dpm, frozen.memories, frozen.compartments, frozen.userProfile, frozen.workspace, mural);
16148
16781
  attempts += 1;
16149
16782
  }
16150
16783
  return {
16151
16784
  m0,
16152
16785
  snapshotMarkers: frozen.markers,
16153
- renderedMemoryIds: renderedMemoryIdsForPi(state, frozen.memories, frozen.workspace, db)
16786
+ renderedMemoryIds: renderedMemoryIdsForPi(state2, frozen.memories, frozen.workspace, db)
16154
16787
  };
16155
16788
  }
16156
- function materializeM0Pi(state, db) {
16157
- const docs = readProjectDocsForPiM0(state);
16789
+ function materializeM0Pi(state2, db) {
16790
+ const docs = readProjectDocsForPiM0(state2);
16158
16791
  const foldMaterializedAt = Date.now();
16159
- const frozen = readFrozenM0InputsPi(state, db, docs, foldMaterializedAt);
16792
+ const frozen = readFrozenM0InputsPi(state2, db, docs, foldMaterializedAt);
16160
16793
  const snapshotMarkers = frozen.markers;
16161
16794
  const snapshotMemories = frozen.memories;
16162
16795
  const snapshotCompartments = frozen.compartments;
16163
16796
  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);
16797
+ const renderedMemoryIds = renderedMemoryIdsForPi(state2, snapshotMemories, frozen.workspace, db);
16798
+ const memoryBudget = state2.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
16799
+ const mural = resolveMuralForM0Pi(state2, db, snapshotMarkers.modelKey, memoryBudget);
16167
16800
  const frozenMuralDataUrl = mural?.enabled && mural.supportsVision ? mural.dataUrl ?? null : null;
16168
16801
  const frozenMuralHash = mural?.enabled && mural.supportsVision ? mural.contentHash ?? null : null;
16169
16802
  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;
16803
+ let m0 = renderM0Pi(state2, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile, frozen.workspace, mural);
16804
+ const historyBudget = state2.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS;
16172
16805
  let attempts = 0;
16173
16806
  while (historyBudget > 0 && historySliceTokensPi(m0) > historyBudget * 1.05 && attempts < 3) {
16174
16807
  decayPressureMultiplier *= 1.15;
16175
- m0 = renderM0Pi(state, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile, frozen.workspace, mural);
16808
+ m0 = renderM0Pi(state2, db, docs.renderedBlock, decayPressureMultiplier, snapshotMemories, snapshotCompartments, snapshotUserProfile, frozen.workspace, mural);
16176
16809
  attempts += 1;
16177
16810
  }
16178
16811
  const m0Bytes = Buffer.from(m0, "utf8");
16179
- const phase3ProjectDocsHash = readProjectDocsForPiM0(state).canonicalHash;
16812
+ const phase3ProjectDocsHash = readProjectDocsForPiM0(state2).canonicalHash;
16180
16813
  try {
16181
16814
  db.exec("BEGIN IMMEDIATE");
16182
16815
  } catch (error) {
@@ -16186,7 +16819,7 @@ function materializeM0Pi(state, db) {
16186
16819
  throw error;
16187
16820
  }
16188
16821
  try {
16189
- const current = readCurrentMarkers(db, state, phase3ProjectDocsHash);
16822
+ const current = readCurrentMarkers(db, state2, phase3ProjectDocsHash);
16190
16823
  const memoryEpochStale = current.workspaceFingerprint !== null || snapshotMarkers.workspaceFingerprint !== null ? current.workspaceFingerprint !== snapshotMarkers.workspaceFingerprint : current.projectMemoryEpoch !== snapshotMarkers.projectMemoryEpoch;
16191
16824
  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
16825
  if (stale) {
@@ -16194,9 +16827,9 @@ function materializeM0Pi(state, db) {
16194
16827
  throw new PiMaterializeContentionError("snapshot changed before persist");
16195
16828
  }
16196
16829
  snapshotMarkers.materializedAt = foldMaterializedAt;
16197
- const m1Render = renderM1PiWithMetadata(state, db, snapshotMarkers, renderedMemoryIds);
16830
+ const m1Render = renderM1PiWithMetadata(state2, db, snapshotMarkers, renderedMemoryIds);
16198
16831
  const m1Bytes = Buffer.from(m1Render.text, "utf8");
16199
- persistCachedM0(db, state.sessionId, {
16832
+ persistCachedM0(db, state2.sessionId, {
16200
16833
  m0Bytes,
16201
16834
  muralDataUrl: frozenMuralDataUrl,
16202
16835
  muralHash: frozenMuralHash,
@@ -16216,10 +16849,10 @@ function materializeM0Pi(state, db) {
16216
16849
  modelKey: snapshotMarkers.modelKey,
16217
16850
  projectIdentity: snapshotMarkers.projectIdentity
16218
16851
  });
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);
16852
+ db.prepare("UPDATE session_meta SET memory_block_count = ?, memory_block_ids = ? WHERE session_id = ?").run(renderedMemoryIds.length, JSON.stringify(renderedMemoryIds), state2.sessionId);
16853
+ setCachedBoundary(db, state2.sessionId, snapshotMarkers.lastBaselineEndMessageId);
16221
16854
  db.exec("COMMIT");
16222
- rememberPiMuralPayload(state.sessionId, frozenMuralDataUrl, frozenMuralHash);
16855
+ rememberPiMuralPayload(state2.sessionId, frozenMuralDataUrl, frozenMuralHash);
16223
16856
  return {
16224
16857
  m0,
16225
16858
  m1: m1Render.text,
@@ -16233,11 +16866,11 @@ function materializeM0Pi(state, db) {
16233
16866
  throw error;
16234
16867
  }
16235
16868
  }
16236
- function materializeM0PiWithRetry(state, db, maxRetries = 3) {
16869
+ function materializeM0PiWithRetry(state2, db, maxRetries = 3) {
16237
16870
  let lastError = null;
16238
16871
  for (let attempt = 0;attempt < maxRetries; attempt++) {
16239
16872
  try {
16240
- return materializeM0Pi(state, db);
16873
+ return materializeM0Pi(state2, db);
16241
16874
  } catch (error) {
16242
16875
  if (!(error instanceof PiMaterializeContentionError))
16243
16876
  throw error;
@@ -16279,10 +16912,10 @@ ${lines.join(`
16279
16912
  count: mutations.length
16280
16913
  };
16281
16914
  }
16282
- function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, compartmentsOverride) {
16915
+ function renderM1PiWithMetadata(state2, db, markers, renderedMemoryIds, compartmentsOverride) {
16283
16916
  const sections = [];
16284
- const workspace = resolveWorkspaceRenderContextPi(state, db);
16285
- const memPath = memoryProjectPath(state);
16917
+ const workspace = resolveWorkspaceRenderContextPi(state2, db);
16918
+ const memPath = memoryProjectPath(state2);
16286
16919
  const memoryUpdates = memPath ? renderMemoryUpdatesBlockPi({
16287
16920
  db,
16288
16921
  projectPath: memPath,
@@ -16292,7 +16925,7 @@ function renderM1PiWithMetadata(state, db, markers, renderedMemoryIds, compartme
16292
16925
  }) : { block: undefined, count: 0 };
16293
16926
  if (memoryUpdates.block)
16294
16927
  sections.push(memoryUpdates.block);
16295
- const newCompartments = (compartmentsOverride ?? getCompartments(db, state.sessionId)).filter((compartment) => compartment.sequence > markers.maxCompartmentSeq);
16928
+ const newCompartments = (compartmentsOverride ?? getRenderableCompartmentsPi(db, state2)).filter((compartment) => compartment.sequence > markers.maxCompartmentSeq);
16296
16929
  if (newCompartments.length > 0) {
16297
16930
  const body = newCompartments.map((compartment) => renderCompartmentAtTier(compartment, 1)).join(`
16298
16931
 
@@ -16303,7 +16936,7 @@ ${body}
16303
16936
  }
16304
16937
  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
16938
  if (newMemories.length > 0) {
16306
- const memoryBudget = state.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
16939
+ const memoryBudget = state2.injectionBudgetTokens ?? DEFAULT_MEMORY_BUDGET_TOKENS;
16307
16940
  const memoryRenderOptions = {
16308
16941
  sourceNameByMemoryId: sourceNamesForPiMemories({
16309
16942
  memories: newMemories,
@@ -16311,14 +16944,14 @@ ${body}
16311
16944
  workspace
16312
16945
  })
16313
16946
  };
16314
- const trimmedNewMemories = trimMemoriesToBudgetV2(state.sessionId, newMemories, Math.max(1, Math.floor(memoryBudget * 0.25)), memoryRenderOptions).renderOrder;
16947
+ const trimmedNewMemories = trimMemoriesToBudgetV2(state2.sessionId, newMemories, Math.max(1, Math.floor(memoryBudget * 0.25)), memoryRenderOptions).renderOrder;
16315
16948
  const newMemoriesBlock = renderMemoryBlockV2(trimmedNewMemories, "new-memories", memoryRenderOptions);
16316
16949
  if (newMemoriesBlock)
16317
16950
  sections.push(newMemoriesBlock);
16318
16951
  }
16319
16952
  const currentUserProfileVersion = getProjectState(db, GLOBAL_USER_PROFILE_PROJECT_PATH)?.projectUserProfileVersion ?? 0;
16320
16953
  if (currentUserProfileVersion !== markers.projectUserProfileVersion) {
16321
- const profileBudget = state.userProfileBudgetTokens ?? DEFAULT_USER_PROFILE_BUDGET_TOKENS;
16954
+ const profileBudget = state2.userProfileBudgetTokens ?? DEFAULT_USER_PROFILE_BUDGET_TOKENS;
16322
16955
  const trimmedProfile = trimUserMemoriesToBudget(safeGetActiveUserMemoriesPi(db), Math.max(1, Math.floor(profileBudget * 0.25)));
16323
16956
  const profileBlock = renderUserProfileBlock(db, "new-user-profile", trimmedProfile);
16324
16957
  if (profileBlock)
@@ -16331,7 +16964,10 @@ ${body}
16331
16964
  };
16332
16965
  }
16333
16966
  return {
16334
- text: `<session-history-since>
16967
+ text: state2.compactionOff ? `<knowledge-updates>
16968
+ ${sections.join(`
16969
+ `)}
16970
+ </knowledge-updates>` : `<session-history-since>
16335
16971
  ${sections.join(`
16336
16972
  `)}
16337
16973
  </session-history-since>`,
@@ -16427,8 +17063,8 @@ function cachedPiRowMatchesSnapshot(args) {
16427
17063
  return false;
16428
17064
  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
17065
  }
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);
17066
+ function adoptCachedPiProjectIdentity(db, state2) {
17067
+ 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
17068
  }
16433
17069
  function decodeCachedM1(row, sessionId) {
16434
17070
  if (!row.cached_m1_bytes) {
@@ -16449,12 +17085,12 @@ function applyCachedPiRow(args) {
16449
17085
  markers
16450
17086
  };
16451
17087
  }
16452
- function replayCachedM1Pi(db, state, compartmentsForNormalization) {
16453
- const row = readCachedPiM0M1Row(db, state.sessionId);
17088
+ function replayCachedM1Pi(db, state2, compartmentsForNormalization) {
17089
+ const row = readCachedPiM0M1Row(db, state2.sessionId);
16454
17090
  if (!row) {
16455
- throw new PiMaterializeContentionError(`missing cached m[0]/m[1] for ${state.sessionId}`);
17091
+ throw new PiMaterializeContentionError(`missing cached m[0]/m[1] for ${state2.sessionId}`);
16456
17092
  }
16457
- return applyCachedPiRow({ row, state, compartmentsForNormalization });
17093
+ return applyCachedPiRow({ row, state: state2, compartmentsForNormalization });
16458
17094
  }
16459
17095
  function softRefreshCachedM1Pi(args) {
16460
17096
  args.db.exec("BEGIN IMMEDIATE");
@@ -16471,7 +17107,7 @@ function softRefreshCachedM1Pi(args) {
16471
17107
  if (!sibling) {
16472
17108
  throw new PiMaterializeContentionError(`missing sibling cached m[0]/m[1] for ${args.state.sessionId}`);
16473
17109
  }
16474
- const siblingCompartments = getCompartments(args.db, args.state.sessionId);
17110
+ const siblingCompartments = getRenderableCompartmentsPi(args.db, args.state);
16475
17111
  return {
16476
17112
  ...applyCachedPiRow({
16477
17113
  row: sibling,
@@ -16538,9 +17174,9 @@ function prependM0M1Messages(piMessages, m0, m1, mural) {
16538
17174
  timestamp: baseTimestamp - 1
16539
17175
  });
16540
17176
  }
16541
- function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = false) {
16542
- const currentCompartments = getCompartments(db, state.sessionId);
16543
- let decision = mustMaterializePi(state, db, currentCompartments);
17177
+ function injectM0M1Pi(state2, db, piMessages, entryIds, recomputeM1ThisPass = false) {
17178
+ const currentCompartments = getRenderableCompartmentsPi(db, state2);
17179
+ let decision = mustMaterializePi(state2, db, currentCompartments);
16544
17180
  let m0 = "";
16545
17181
  let m1 = PI_M1_PLACEHOLDER;
16546
17182
  let markers = null;
@@ -16551,7 +17187,7 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16551
17187
  let freshFallbackRenderedMemoryIds = null;
16552
17188
  if (decision.value) {
16553
17189
  try {
16554
- const result = materializeM0PiWithRetry(state, db);
17190
+ const result = materializeM0PiWithRetry(state2, db);
16555
17191
  m0 = result.m0;
16556
17192
  m1 = result.m1;
16557
17193
  markers = result.snapshotMarkers;
@@ -16561,30 +17197,30 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16561
17197
  if (!(error instanceof PiMaterializeContentionError))
16562
17198
  throw error;
16563
17199
  try {
16564
- const cached = replayCachedM1Pi(db, state, currentCompartments);
17200
+ const cached = replayCachedM1Pi(db, state2, currentCompartments);
16565
17201
  contentionExhausted = true;
16566
17202
  m0 = cached.m0;
16567
17203
  m1 = cached.m1;
16568
17204
  markers = cached.markers;
16569
- sessionLog(state.sessionId, "pi m[0] materialization contention exhausted; reusing cached m[0]/m[1]");
17205
+ sessionLog(state2.sessionId, "pi m[0] materialization contention exhausted; reusing cached m[0]/m[1]");
16570
17206
  } catch {
16571
- const fresh = renderFreshM0PiNonPersisted(state, db);
17207
+ const fresh = renderFreshM0PiNonPersisted(state2, db);
16572
17208
  m0 = fresh.m0;
16573
17209
  markers = fresh.snapshotMarkers;
16574
17210
  freshFallbackRenderedMemoryIds = fresh.renderedMemoryIds;
16575
17211
  contentionExhausted = true;
16576
- sessionLog(state.sessionId, "pi m[0] materialization contention exhausted with no cached fallback; rendered fresh non-persisted m[0]/m[1]");
17212
+ sessionLog(state2.sessionId, "pi m[0] materialization contention exhausted with no cached fallback; rendered fresh non-persisted m[0]/m[1]");
16577
17213
  }
16578
17214
  }
16579
17215
  } else {
16580
- const meta = getOrCreateSessionMeta(db, state.sessionId);
17216
+ const meta = getOrCreateSessionMeta(db, state2.sessionId);
16581
17217
  m0 = decodeCachedM0(meta.cachedM0Bytes) ?? "";
16582
- rememberPiMuralPayload(state.sessionId, meta.cachedM0MuralDataUrl, meta.cachedM0MuralHash);
16583
- markers = getCachedMarkers(db, state, currentCompartments);
17218
+ rememberPiMuralPayload(state2.sessionId, meta.cachedM0MuralDataUrl, meta.cachedM0MuralHash);
17219
+ markers = getCachedMarkers(db, state2, currentCompartments);
16584
17220
  if (!m0 || !markers) {
16585
17221
  decision = { value: true, reason: "cache_invalid" };
16586
17222
  try {
16587
- const result = materializeM0PiWithRetry(state, db);
17223
+ const result = materializeM0PiWithRetry(state2, db);
16588
17224
  m0 = result.m0;
16589
17225
  m1 = result.m1;
16590
17226
  markers = result.snapshotMarkers;
@@ -16593,30 +17229,30 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16593
17229
  } catch (error) {
16594
17230
  if (!(error instanceof PiMaterializeContentionError))
16595
17231
  throw error;
16596
- const fresh = renderFreshM0PiNonPersisted(state, db);
17232
+ const fresh = renderFreshM0PiNonPersisted(state2, db);
16597
17233
  m0 = fresh.m0;
16598
17234
  markers = fresh.snapshotMarkers;
16599
17235
  freshFallbackRenderedMemoryIds = fresh.renderedMemoryIds;
16600
17236
  contentionExhausted = true;
16601
- sessionLog(state.sessionId, "pi m[0] cache_invalid materialization contention exhausted; rendered fresh non-persisted m[0]/m[1]");
17237
+ sessionLog(state2.sessionId, "pi m[0] cache_invalid materialization contention exhausted; rendered fresh non-persisted m[0]/m[1]");
16602
17238
  }
16603
17239
  }
16604
17240
  }
16605
17241
  if (!markers) {
16606
- throw new PiMaterializeContentionError(`missing m[0] markers for ${state.sessionId}`);
17242
+ throw new PiMaterializeContentionError(`missing m[0] markers for ${state2.sessionId}`);
16607
17243
  }
16608
17244
  if (!materialized && markers.projectIdentity === null) {
16609
- adoptCachedPiProjectIdentity(db, state);
16610
- markers = { ...markers, projectIdentity: state.projectIdentity };
17245
+ adoptCachedPiProjectIdentity(db, state2);
17246
+ markers = { ...markers, projectIdentity: state2.projectIdentity };
16611
17247
  }
16612
17248
  if (materialized) {} else if (contentionExhausted && freshFallbackRenderedMemoryIds) {
16613
- const freshM1 = renderM1PiWithMetadata(state, db, markers, freshFallbackRenderedMemoryIds);
17249
+ const freshM1 = renderM1PiWithMetadata(state2, db, markers, freshFallbackRenderedMemoryIds);
16614
17250
  m1 = freshM1.text;
16615
17251
  memoryUpdateCount = freshM1.memoryUpdateCount;
16616
17252
  m1Recomputed = true;
16617
17253
  } else if (contentionExhausted) {} else if (recomputeM1ThisPass) {
16618
17254
  const refreshed = softRefreshCachedM1Pi({
16619
- state,
17255
+ state: state2,
16620
17256
  db,
16621
17257
  m0Bytes: Buffer.from(m0, "utf8"),
16622
17258
  markers,
@@ -16628,7 +17264,7 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16628
17264
  memoryUpdateCount = refreshed.memoryUpdateCount;
16629
17265
  m1Recomputed = refreshed.recomputed;
16630
17266
  } else {
16631
- const replayed = replayCachedM1Pi(db, state, currentCompartments);
17267
+ const replayed = replayCachedM1Pi(db, state2, currentCompartments);
16632
17268
  m0 = replayed.m0;
16633
17269
  m1 = replayed.m1;
16634
17270
  markers = replayed.markers;
@@ -16636,14 +17272,14 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16636
17272
  const M0_DRIFT_RATIO_FLOOR_TOKENS = 500;
16637
17273
  const M1_DRIFT_RATIO = 0.15;
16638
17274
  const M1_ABSOLUTE_CAP_RATIO = 0.2;
16639
- const m1AbsoluteBudget = (state.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS) * M1_ABSOLUTE_CAP_RATIO;
17275
+ const m1AbsoluteBudget = (state2.historyBudgetTokens ?? DEFAULT_HISTORY_BUDGET_TOKENS) * M1_ABSOLUTE_CAP_RATIO;
16640
17276
  const m1HasContent = m1 !== PI_M1_PLACEHOLDER;
16641
- const { m0Tokens, m1Tokens } = cachedInjectionTokenCounts(state.sessionId, m0, m1);
17277
+ const { m0Tokens, m1Tokens } = cachedInjectionTokenCounts(state2.sessionId, m0, m1);
16642
17278
  const m1OverAbsoluteCap = m1HasContent && m1Tokens > m1AbsoluteBudget;
16643
17279
  if (!materialized && !contentionExhausted && m1Recomputed && recomputeM1ThisPass && (memoryUpdateCount > 40 || m1OverAbsoluteCap || m1HasContent && m0Tokens >= M0_DRIFT_RATIO_FLOOR_TOKENS && m1Tokens > m0Tokens * M1_DRIFT_RATIO)) {
16644
17280
  decision = { value: true, reason: "drift" };
16645
17281
  try {
16646
- const result = materializeM0PiWithRetry(state, db);
17282
+ const result = materializeM0PiWithRetry(state2, db);
16647
17283
  m0 = result.m0;
16648
17284
  m1 = result.m1;
16649
17285
  markers = result.snapshotMarkers;
@@ -16666,17 +17302,17 @@ function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = fal
16666
17302
  }
16667
17303
  }
16668
17304
  const skippedVisibleMessages = boundaryId ? trimPiMessagesToBoundary(piMessages, entryIds, boundaryId) : 0;
16669
- const muralWire = m0.includes("<memory-mural>") ? muralForWire(state.sessionId) : undefined;
17305
+ const muralWire = m0.includes("<memory-mural>") ? muralForWire(state2.sessionId) : undefined;
16670
17306
  if (!muralWire)
16671
17307
  m0 = stripMemoryMuralBlock(m0);
16672
17308
  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);
17309
+ sessionLog(state2.sessionId, `injected m[0]/m[1] into Pi messages (${m0.length} + ${m1.length} bytes, materialized=${materialized}${decision.reason ? ` reason=${decision.reason}` : ""})`);
17310
+ const memPath = memoryProjectPath(state2);
17311
+ const workspace = resolveWorkspaceRenderContextPi(state2, db);
16676
17312
  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
17313
  return {
16678
17314
  injected: true,
16679
- compartmentCount: getCompartments(db, state.sessionId).length,
17315
+ compartmentCount: currentCompartments.length,
16680
17316
  factCount: 0,
16681
17317
  memoryCount,
16682
17318
  skippedVisibleMessages,
@@ -16738,6 +17374,16 @@ function isSentinelPart(part) {
16738
17374
  return p.type === "text" && typeof p.text === "string" && (p.text === "" || p.text === WHOLE_MESSAGE_PLACEHOLDER_TEXT);
16739
17375
  }
16740
17376
 
17377
+ // src/pi-context-limit.ts
17378
+ function resolvePiUsableContextLimit(args) {
17379
+ const rawContext = isSaneLimit(args.rawContextWindow) ? args.rawContextWindow : undefined;
17380
+ const detected = typeof args.detectedContextLimit === "number" && Number.isFinite(args.detectedContextLimit) && args.detectedContextLimit >= 1024 ? args.detectedContextLimit : undefined;
17381
+ const context = rawContext !== undefined && detected !== undefined ? Math.min(rawContext, detected) : rawContext ?? detected;
17382
+ if (context === undefined)
17383
+ return;
17384
+ return resolveLimit({ context, output: args.model?.maxTokens }, args.model?.provider ?? "unknown", args.model?.id ?? "unknown");
17385
+ }
17386
+
16741
17387
  // src/pi-historian-runner.ts
16742
17388
  import * as crypto2 from "node:crypto";
16743
17389
 
@@ -19928,6 +20574,7 @@ async function runPiHistorian(deps) {
19928
20574
  twoPass,
19929
20575
  thinkingLevel,
19930
20576
  memoryEnabled,
20577
+ allowHomeProject,
19931
20578
  autoPromote,
19932
20579
  userMemoriesEnabled,
19933
20580
  onPublished,
@@ -20066,7 +20713,7 @@ async function runPiHistorian(deps) {
20066
20713
  rollbackDrainReservation();
20067
20714
  return;
20068
20715
  }
20069
- const projectPath = resolveProjectIdentityForSession(directory);
20716
+ const projectPath = resolveProjectIdentityForSession(directory, allowHomeProject);
20070
20717
  if (!projectPath) {
20071
20718
  rollbackDrainReservation();
20072
20719
  return;
@@ -22021,7 +22668,6 @@ function extractStableId(msg, index, entryIds) {
22021
22668
  }
22022
22669
 
22023
22670
  // src/context-handler.ts
22024
- var FORCE_MATERIALIZATION_PERCENTAGE = 85;
22025
22671
  var EMERGENCY_BLOCK_PERCENTAGE = 95;
22026
22672
  var FORWARD_PRESSURE_LIMIT_FACTOR = 0.85;
22027
22673
  function applyForwardPressureFloor(trailingPercentage, trailingInputTokens, piUsageTokens, correctedLimit) {
@@ -22643,8 +23289,8 @@ function parsePiFallbackToolOwnerId(ownerMsgId) {
22643
23289
  return { timestamp, role: match[2] ?? "" };
22644
23290
  }
22645
23291
  function databaseIsInTransaction(db) {
22646
- const state = db;
22647
- return state.inTransaction === true || state.isTransaction === true;
23292
+ const state2 = db;
23293
+ return state2.inTransaction === true || state2.isTransaction === true;
22648
23294
  }
22649
23295
  function runImmediateTransaction(db, fn) {
22650
23296
  if (databaseIsInTransaction(db)) {
@@ -22770,7 +23416,7 @@ function registerPiContextHandler(pi, baseOptions) {
22770
23416
  const options = baseOptions.resolveForProject?.(projectDirectory) ?? baseOptions;
22771
23417
  const schedulerConfig = options.scheduler ?? DEFAULT_SCHEDULER_CONFIG;
22772
23418
  const scheduler = schedulerFor(options);
22773
- const projectIdentity = resolveProjectIdentityForSession(projectDirectory) ?? "";
23419
+ const projectIdentity = resolveProjectIdentityForSession(projectDirectory, options.allowHomeProject) ?? "";
22774
23420
  updateSessionProjectTracking(sessionId, projectIdentity, options.db);
22775
23421
  logTransformTiming(sessionId, "findSessionId", tFindSession, `messages=${event.messages.length}`);
22776
23422
  const tEntryBranch = performance.now();
@@ -22792,7 +23438,7 @@ function registerPiContextHandler(pi, baseOptions) {
22792
23438
  const alignedEntryIds = branchLookup?.alignedEntryIds ?? null;
22793
23439
  const resolvedEntryIds = alignedEntryIds?.length === event.messages.length ? alignedEntryIds : branchEntries === null ? null : collectMessageEntryIdsByRef(ctx, event.messages, sessionId, branchEntries);
22794
23440
  const strictEntryIds = resolvedEntryIds ? [...resolvedEntryIds] : null;
22795
- if (strictEntryIds && options.injection) {
23441
+ if (strictEntryIds && options.injection && !options.compactionOff) {
22796
23442
  const removed = trimPiMessagesToCachedBoundary(options.db, sessionId, event.messages, strictEntryIds);
22797
23443
  if (removed > 0) {
22798
23444
  logTransformTiming(sessionId, "cachedBoundaryEarlyTrim", tEntryBranch, `removed=${removed}`);
@@ -22840,6 +23486,30 @@ function registerPiContextHandler(pi, baseOptions) {
22840
23486
  const tMeta = performance.now();
22841
23487
  const sessionMetaForUsage = getOrCreateSessionMeta(options.db, sessionId);
22842
23488
  logTransformTiming(sessionId, "getOrCreateSessionMeta", tMeta);
23489
+ const compactionTransition = reconcilePiCompactionMode({
23490
+ db: options.db,
23491
+ sessionId,
23492
+ compactionOff: options.compactionOff === true,
23493
+ historianRunnable: options.historian !== undefined
23494
+ });
23495
+ if (compactionTransition.recordToWrite) {
23496
+ if (compactionTransition.clearDeferredMarkerState) {
23497
+ clearPiCompactionOffInMemoryState(sessionId);
23498
+ }
23499
+ if (compactionTransition.invalidatedBaseline) {
23500
+ clearPiInjectionTokenCountCache(sessionId);
23501
+ sessionMetaForUsage.cachedM0Bytes = null;
23502
+ sessionMetaForUsage.cachedM1Bytes = null;
23503
+ }
23504
+ let noticeDelivered = compactionTransition.notice === null;
23505
+ if (compactionTransition.notice && ctx.ui?.notify) {
23506
+ ctx.ui.notify(compactionTransition.notice, "info");
23507
+ noticeDelivered = true;
23508
+ }
23509
+ if (noticeDelivered) {
23510
+ commitPiCompactionModeRecord(options.db, sessionId, compactionTransition.recordToWrite);
23511
+ }
23512
+ }
22843
23513
  const usageReset = {
22844
23514
  lastContextPercentage: 0,
22845
23515
  lastInputTokens: 0,
@@ -22883,12 +23553,14 @@ function registerPiContextHandler(pi, baseOptions) {
22883
23553
  usageInputTokens = typeof piUsage?.tokens === "number" ? piUsage.tokens : 0;
22884
23554
  }
22885
23555
  let usageContextLimit = isSaneLimit(piUsage?.contextWindow) ? piUsage.contextWindow : undefined;
23556
+ let detectedContextLimit;
22886
23557
  const tEmergencyRecovery = performance.now();
22887
23558
  let needsEmergencyBump = false;
22888
23559
  let emergencyRecoveryArmed = false;
22889
23560
  try {
22890
- const overflowState = getOverflowState(options.db, sessionId);
23561
+ const overflowState = options.compactionOff ? { detectedContextLimit: 0, needsEmergencyRecovery: false } : getOverflowState(options.db, sessionId);
22891
23562
  if (overflowState.detectedContextLimit > 0) {
23563
+ detectedContextLimit = overflowState.detectedContextLimit;
22892
23564
  usageContextLimit = Math.min(usageContextLimit ?? overflowState.detectedContextLimit, overflowState.detectedContextLimit);
22893
23565
  }
22894
23566
  emergencyRecoveryArmed = overflowState.needsEmergencyRecovery;
@@ -22906,6 +23578,17 @@ function registerPiContextHandler(pi, baseOptions) {
22906
23578
  usageContextLimit = modelWindow;
22907
23579
  }
22908
23580
  }
23581
+ usageContextLimit = resolvePiUsableContextLimit({
23582
+ rawContextWindow: usageContextLimit,
23583
+ model: ctx.model,
23584
+ detectedContextLimit
23585
+ });
23586
+ const effectiveExecuteThresholdPercentage = resolveExecuteThreshold(schedulerConfig.executeThresholdPercentage, modelKey, 65, {
23587
+ tokensConfig: schedulerConfig.executeThresholdTokens,
23588
+ contextLimit: usageContextLimit,
23589
+ sessionId
23590
+ });
23591
+ const { forceMaterializationPercentage } = escalationBands(effectiveExecuteThresholdPercentage);
22909
23592
  if (!usedPersistedUsage && isSaneLimit(usageContextLimit) && usageInputTokens > 0) {
22910
23593
  usagePercentage = usageInputTokens / usageContextLimit * 100;
22911
23594
  }
@@ -22926,7 +23609,7 @@ function registerPiContextHandler(pi, baseOptions) {
22926
23609
  logTransformTiming(sessionId, "schedulerAndUsage", tScheduler);
22927
23610
  const piMessageCount = fullWireMessageCount;
22928
23611
  const looksLikeImportedSession = schedulerDecision === "defer" && usagePercentage === 0 && sessionMeta.lastResponseTime === 0 && piMessageCount >= 50;
22929
- if (looksLikeImportedSession) {
23612
+ if (looksLikeImportedSession && !options.compactionOff) {
22930
23613
  schedulerDecision = "execute";
22931
23614
  sessionLog(sessionId, `transform: large imported session detected (${piMessageCount} messages, no usage baseline) — forcing execute on first pass`);
22932
23615
  }
@@ -22937,7 +23620,7 @@ function registerPiContextHandler(pi, baseOptions) {
22937
23620
  if (storedStableIdScheme < PI_STABLE_ID_SCHEME && !realEntryIdsAvailable) {
22938
23621
  sessionLog(sessionId, `stable-id scheme cutover deferred: real SessionEntry ids unavailable this pass (branch resolution failed) — will retry when getBranch() succeeds`);
22939
23622
  }
22940
- if (stableIdSchemeCutover) {
23623
+ if (stableIdSchemeCutover && !options.compactionOff) {
22941
23624
  schedulerDecision = "execute";
22942
23625
  signalPiPendingMaterialization(sessionId);
22943
23626
  sessionLog(sessionId, `stable-id scheme cutover: stored=${storedStableIdScheme} < current=${PI_STABLE_ID_SCHEME} — forcing execute+materialize this pass`);
@@ -22949,14 +23632,18 @@ function registerPiContextHandler(pi, baseOptions) {
22949
23632
  contextUsage: { percentage: usagePercentage },
22950
23633
  sessionMeta,
22951
23634
  historyRefreshSessions,
22952
- sessionId
23635
+ sessionId,
23636
+ effectiveExecuteThresholdPercentage
22953
23637
  });
22954
- const { midTurnAdjustedSchedulerDecision, sideEffect } = applyMidTurnDeferral({
23638
+ const { midTurnAdjustedSchedulerDecision, sideEffect } = options.compactionOff ? {
23639
+ midTurnAdjustedSchedulerDecision: "defer",
23640
+ sideEffect: "none"
23641
+ } : applyMidTurnDeferral({
22955
23642
  base: schedulerDecisionEarly,
22956
23643
  bypassReason,
22957
23644
  midTurn
22958
23645
  });
22959
- if (sideEffect === "set-flag") {
23646
+ if (sideEffect === "set-flag" && !options.compactionOff) {
22960
23647
  const flagPayload = {
22961
23648
  id: crypto3.randomUUID(),
22962
23649
  reason: `${schedulerDecisionEarly}-${bypassReason}`,
@@ -22966,8 +23653,8 @@ function registerPiContextHandler(pi, baseOptions) {
22966
23653
  }
22967
23654
  schedulerDecision = midTurnAdjustedSchedulerDecision;
22968
23655
  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;
23656
+ const forceMaterialization = !options.compactionOff && usagePercentage >= forceMaterializationPercentage;
23657
+ const isEmergency = !options.compactionOff && usagePercentage >= EMERGENCY_BLOCK_PERCENTAGE;
22971
23658
  if (isEmergency) {
22972
23659
  const lastNotifiedAt = lastEmergencyNotificationAtMs.get(sessionId) ?? 0;
22973
23660
  const now = Date.now();
@@ -22983,7 +23670,7 @@ function registerPiContextHandler(pi, baseOptions) {
22983
23670
  sessionLog(sessionId, "EMERGENCY: historian wait completed (or timed out)");
22984
23671
  } catch {}
22985
23672
  }
22986
- if (emergencyRecoveryArmed && realUsagePercentageBeforeEmergencyBump < FORCE_MATERIALIZATION_PERCENTAGE && !inFlightHistorian.has(sessionId) && !hasEligiblePiCompartmentHistory(options.db, sessionId)) {
23673
+ if (emergencyRecoveryArmed && realUsagePercentageBeforeEmergencyBump < forceMaterializationPercentage && !inFlightHistorian.has(sessionId) && !hasEligiblePiCompartmentHistory(options.db, sessionId)) {
22987
23674
  try {
22988
23675
  clearEmergencyRecovery(options.db, sessionId);
22989
23676
  sessionLog(sessionId, "EMERGENCY: disarming recovery — no eligible pre-tail history to compact (would otherwise loop at 95%)");
@@ -23035,6 +23722,7 @@ function registerPiContextHandler(pi, baseOptions) {
23035
23722
  stableIdSchemeCutover,
23036
23723
  schedulerDecision,
23037
23724
  forceMaterialization: forceMaterialization || isEmergency,
23725
+ forceMaterializationPercentage,
23038
23726
  contextUsage: {
23039
23727
  percentage: usagePercentage,
23040
23728
  inputTokens: usageInputTokens
@@ -23047,7 +23735,8 @@ function registerPiContextHandler(pi, baseOptions) {
23047
23735
  temporalAwareness: options.injection?.temporalAwareness === true,
23048
23736
  appendCompaction: resolvePiAppendCompaction(ctx),
23049
23737
  readBranchEntries: resolvePiReadBranchEntries(ctx),
23050
- isSubagent: sessionMeta.isSubagent
23738
+ isSubagent: sessionMeta.isSubagent,
23739
+ compactionOff: options.compactionOff === true
23051
23740
  });
23052
23741
  logTransformTiming(sessionId, "runPipeline", tRunPipeline);
23053
23742
  const postPipelineStart = performance.now();
@@ -23071,7 +23760,7 @@ function registerPiContextHandler(pi, baseOptions) {
23071
23760
  }
23072
23761
  logTransformTiming(sessionId, "transformDecisionAndReuseState", tTransformDecision);
23073
23762
  const tHistorianScheduling = performance.now();
23074
- if (options.historian) {
23763
+ if (options.historian && !options.compactionOff) {
23075
23764
  maybeFireHistorian({
23076
23765
  pi,
23077
23766
  ctx,
@@ -23089,22 +23778,24 @@ function registerPiContextHandler(pi, baseOptions) {
23089
23778
  let outputMessages = result.messages;
23090
23779
  const tNoteNudges = performance.now();
23091
23780
  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
- });
23781
+ if (!options.compactionOff) {
23782
+ outputMessages = applyNoteNudges({
23783
+ sessionId,
23784
+ db: options.db,
23785
+ messages: outputMessages,
23786
+ projectIdentity,
23787
+ entryIds: strictEntryIds,
23788
+ entryIdByRef: result.postCommitEntryIdByRef,
23789
+ isCacheBusting: isCacheBusting || result.executedWorkThisPass,
23790
+ syntheticLeadingCount: result.syntheticLeadingCount
23791
+ });
23792
+ }
23102
23793
  } catch (err) {
23103
23794
  sessionLog(sessionId, `note nudges failed: ${err instanceof Error ? err.message : String(err)}`);
23104
23795
  }
23105
23796
  logTransformTiming(sessionId, "noteNudges", tNoteNudges);
23106
23797
  const tAutoSearch = performance.now();
23107
- if (options.autoSearch?.enabled) {
23798
+ if (options.autoSearch?.enabled && !options.compactionOff) {
23108
23799
  try {
23109
23800
  outputMessages = await runAutoSearchHintForPi({
23110
23801
  sessionId,
@@ -23129,7 +23820,7 @@ function registerPiContextHandler(pi, baseOptions) {
23129
23820
  const tTodoCapture = performance.now();
23130
23821
  try {
23131
23822
  const sessionMetaForTodo = getOrCreateSessionMeta(options.db, sessionId);
23132
- if (!sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
23823
+ if (!options.compactionOff && !sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
23133
23824
  const isCacheBustingForTodo = isCacheBusting || result.executedWorkThisPass;
23134
23825
  outputMessages = injectSyntheticTodowriteForPi({
23135
23826
  db: options.db,
@@ -23147,7 +23838,7 @@ function registerPiContextHandler(pi, baseOptions) {
23147
23838
  const tChannelAccounting = performance.now();
23148
23839
  try {
23149
23840
  const sessionMetaForCh1 = getOrCreateSessionMeta(options.db, sessionId);
23150
- if (!sessionMetaForCh1.isSubagent) {
23841
+ if (!options.compactionOff && !sessionMetaForCh1.isSubagent) {
23151
23842
  const resolvedExecuteThresholdPct = resolveExecuteThreshold(schedulerConfig.executeThresholdPercentage ?? 65, liveModelBySession.get(sessionId), 65, {
23152
23843
  tokensConfig: schedulerConfig.executeThresholdTokens,
23153
23844
  contextLimit: usageContextLimit ?? 0
@@ -23214,7 +23905,7 @@ function registerPiContextHandler(pi, baseOptions) {
23214
23905
  }
23215
23906
  logTransformTiming(sessionId, "workMetrics", tWorkMetrics);
23216
23907
  const tStableIdSchemePersist = performance.now();
23217
- if (stableIdSchemeCutover) {
23908
+ if (stableIdSchemeCutover && !options.compactionOff) {
23218
23909
  persistStableIdSchemeForRun(options.db, sessionId, {
23219
23910
  piStableIdScheme: PI_STABLE_ID_SCHEME
23220
23911
  });
@@ -23239,7 +23930,7 @@ function registerPiContextHandler(pi, baseOptions) {
23239
23930
  sessionLog(sessionId, `transform completed in ${transformElapsedMs.toFixed(1)}ms (${outputMessages.length} messages, ${result.targetCount} targets, watermark: ${result.reasoningWatermark})`);
23240
23931
  return { messages: outputMessages };
23241
23932
  } catch (err) {
23242
- if (isFailClosedBlockingError(err))
23933
+ if (isFailClosedBlockingError(err) && !baseOptions.compactionOff)
23243
23934
  throw err;
23244
23935
  const message = err instanceof Error ? err.message : String(err);
23245
23936
  const stack = err instanceof Error ? err.stack : undefined;
@@ -23396,6 +24087,7 @@ function spawnPiHistorianRun(args) {
23396
24087
  twoPass: historian.twoPass,
23397
24088
  thinkingLevel: historian.thinkingLevel,
23398
24089
  memoryEnabled: historian.memoryEnabled,
24090
+ allowHomeProject: historian.allowHomeProject,
23399
24091
  autoPromote: historian.autoPromote,
23400
24092
  userMemoriesEnabled: historian.userMemoriesEnabled,
23401
24093
  language: historian.language,
@@ -23475,15 +24167,22 @@ function maybeFireHistorian(args) {
23475
24167
  const piUsage = ctx.getContextUsage?.();
23476
24168
  let usageSource;
23477
24169
  usageContextLimit = isSaneLimit(piUsage?.contextWindow) ? piUsage.contextWindow : undefined;
24170
+ let detectedContextLimit;
23478
24171
  if (usageContextLimit === undefined && isSaneLimit(ctx.model?.contextWindow)) {
23479
24172
  usageContextLimit = ctx.model.contextWindow;
23480
24173
  }
23481
24174
  try {
23482
24175
  const overflowState = getOverflowState(db, sessionId);
23483
24176
  if (overflowState.detectedContextLimit > 0) {
24177
+ detectedContextLimit = overflowState.detectedContextLimit;
23484
24178
  usageContextLimit = Math.min(usageContextLimit ?? overflowState.detectedContextLimit, overflowState.detectedContextLimit);
23485
24179
  }
23486
24180
  } catch {}
24181
+ usageContextLimit = resolvePiUsableContextLimit({
24182
+ rawContextWindow: usageContextLimit,
24183
+ model: ctx.model,
24184
+ detectedContextLimit
24185
+ });
23487
24186
  const sessionMetaForUsage = getOrCreateSessionMeta(db, sessionId);
23488
24187
  if (sessionMetaForUsage.lastContextPercentage > 0 && sessionMetaForUsage.lastInputTokens > 0) {
23489
24188
  usage = {
@@ -23522,6 +24221,7 @@ function maybeFireHistorian(args) {
23522
24221
  modelKey,
23523
24222
  usageContextLimit
23524
24223
  });
24224
+ const historianForceMaterializationPercentage = escalationBands(triggerInputs.executeThresholdPercentage).forceMaterializationPercentage;
23525
24225
  const boundaryContextLimit = triggerInputs.contextLimit;
23526
24226
  const resolvePiBoundarySnapshot = (emergencyTailScale) => resolveProtectedTailBoundary(resolveBoundaryContext({
23527
24227
  db,
@@ -23537,7 +24237,7 @@ function maybeFireHistorian(args) {
23537
24237
  }));
23538
24238
  const resolveRunnablePiBoundarySnapshot = () => {
23539
24239
  let snapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot());
23540
- if (!hasRunnableCompartmentWindow(snapshot) && usage.percentage >= 80) {
24240
+ if (!hasRunnableCompartmentWindow(snapshot) && usage.percentage >= historianForceMaterializationPercentage) {
23541
24241
  snapshot = ensureRunnablePiBoundaryForTests(resolvePiBoundarySnapshot(usage.percentage >= 95 ? 0.25 : 0.5));
23542
24242
  }
23543
24243
  return snapshot;
@@ -23581,15 +24281,15 @@ Historian previously failed ${failureState.failureCount} time(s), so Magic Conte
23581
24281
  const trigger = checkCompartmentTrigger(db, sessionId, sessionMeta, usage, 0, triggerInputs.executeThresholdPercentage, triggerInputs.triggerBudget, triggerInputs.clearReasoningAge, triggerInputs.commitClusterTrigger, args.activeTags, boundaryContextLimit, () => {
23582
24282
  const messages = provider.readMessages();
23583
24283
  return { messages, absoluteMessageCount: messages.length };
23584
- }, args.taggerFloor);
24284
+ }, args.taggerFloor, { canClearReasoning: true });
23585
24285
  if (!trigger.shouldFire) {
23586
24286
  sessionLog(sessionId, `historian trigger eval: shouldFire=false (no trigger condition met)`);
23587
24287
  try {
23588
24288
  const overflowState = getOverflowState(db, sessionId);
23589
- if (overflowState.needsEmergencyRecovery && usage.percentage < FORCE_MATERIALIZATION_PERCENTAGE && !inFlightHistorian.has(sessionId)) {
24289
+ if (overflowState.needsEmergencyRecovery && usage.percentage < historianForceMaterializationPercentage && !inFlightHistorian.has(sessionId)) {
23590
24290
  boundarySnapshot ??= resolveRunnablePiBoundarySnapshot();
23591
24291
  }
23592
- if (overflowState.needsEmergencyRecovery && usage.percentage < FORCE_MATERIALIZATION_PERCENTAGE && !inFlightHistorian.has(sessionId) && boundarySnapshot !== undefined && !hasRunnableCompartmentWindow(boundarySnapshot)) {
24292
+ if (overflowState.needsEmergencyRecovery && usage.percentage < historianForceMaterializationPercentage && !inFlightHistorian.has(sessionId) && boundarySnapshot !== undefined && !hasRunnableCompartmentWindow(boundarySnapshot)) {
23593
24293
  clearEmergencyRecovery(db, sessionId);
23594
24294
  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
24295
  }
@@ -23677,7 +24377,45 @@ function captureReasoningMutationRollback(messages) {
23677
24377
  }
23678
24378
  };
23679
24379
  }
24380
+ async function runCompactionOffPipeline(args) {
24381
+ let injectionResult = null;
24382
+ if (args.injection) {
24383
+ injectionResult = injectM0M1Pi({
24384
+ sessionId: args.sessionId,
24385
+ projectIdentity: args.projectIdentity,
24386
+ projectDirectory: args.projectDirectory,
24387
+ memoryEnabled: args.injection.memoryEnabled,
24388
+ injectDocs: args.injection.injectDocs,
24389
+ injectionBudgetTokens: args.injection.injectionBudgetTokens,
24390
+ historyBudgetTokens: args.injection.historyBudgetTokens,
24391
+ muralEnabled: args.injection.muralEnabled === true,
24392
+ compactionOff: true
24393
+ }, args.db, args.messages, args.entryIds, false);
24394
+ }
24395
+ return {
24396
+ messages: args.messages,
24397
+ heuristicsExecuted: false,
24398
+ executedWorkThisPass: false,
24399
+ historyInjected: false,
24400
+ syntheticLeadingCount: injectionResult?.syntheticLeadingCount ?? 0,
24401
+ heuristicsResult: null,
24402
+ injectionResult,
24403
+ materialized: injectionResult?.m0Materialized === true,
24404
+ materializeReason: injectionResult?.m0Reason ?? null,
24405
+ droppedTokens: 0,
24406
+ droppedCount: 0,
24407
+ emergency: false,
24408
+ bustedThisPass: injectionResult?.m0Materialized === true,
24409
+ targetCount: 0,
24410
+ reasoningWatermark: args.sessionMeta.clearedReasoningThroughTag ?? 0,
24411
+ activeTags: [],
24412
+ postCommitEntryIdByRef: new Map
24413
+ };
24414
+ }
23680
24415
  async function runPipeline(args) {
24416
+ if (args.compactionOff)
24417
+ return runCompactionOffPipeline(args);
24418
+ const forceMaterializationPercentage = args.forceMaterializationPercentage ?? escalationBands(65).forceMaterializationPercentage;
23681
24419
  let executedWorkThisPass = false;
23682
24420
  let historyWasConsumedThisPass = false;
23683
24421
  let materializationSatisfiedThisPass = false;
@@ -23716,7 +24454,7 @@ async function runPipeline(args) {
23716
24454
  })();
23717
24455
  const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
23718
24456
  const ctxReduceCallable = !args.sessionMeta.isSubagent;
23719
- const canConsumeDeferredLate = args.schedulerDecision === "execute" || args.forceMaterialization === true || args.contextUsage.percentage >= FORCE_MATERIALIZATION_PERCENTAGE;
24457
+ const canConsumeDeferredLate = args.schedulerDecision === "execute" || args.forceMaterialization === true || args.contextUsage.percentage >= forceMaterializationPercentage;
23720
24458
  const deferredMaterializeEligible = canConsumeDeferredLate && deferredMaterializationSessions.has(args.sessionId);
23721
24459
  const piHardSignals = args.injection ? (() => {
23722
24460
  const hardMeta = args.sessionMeta;
@@ -23966,7 +24704,7 @@ async function runPipeline(args) {
23966
24704
  }
23967
24705
  const toolReclaimExecutePass = args.schedulerDecision === "execute";
23968
24706
  const alreadyMutatingThisPass = pendingOpsDidMutate || heuristicOrReasoningDidMutate;
23969
- const emergencyDropEligible = args.forceMaterialization === true || args.contextUsage.percentage >= FORCE_MATERIALIZATION_PERCENTAGE;
24707
+ const emergencyDropEligible = args.forceMaterialization === true || args.contextUsage.percentage >= forceMaterializationPercentage;
23970
24708
  let autoReclaimTargetCount = 0;
23971
24709
  let autoReclaimDidMutate = false;
23972
24710
  if (toolReclaimExecutePass && alreadyMutatingThisPass && !emergencyDropEligible) {
@@ -24353,6 +25091,13 @@ function appendReminderToPiUserMessage(message, reminder) {
24353
25091
  }
24354
25092
  contentArr.push({ type: "text", text: reminder.trimStart() });
24355
25093
  }
25094
+ function clearPiCompactionOffInMemoryState(sessionId) {
25095
+ historyRefreshSessions.delete(sessionId);
25096
+ deferredHistoryRefreshSessions.delete(sessionId);
25097
+ pendingMaterializationSessions.delete(sessionId);
25098
+ deferredMaterializationSessions.delete(sessionId);
25099
+ clearPiChannel1State(sessionId);
25100
+ }
24356
25101
  function clearContextHandlerSession(sessionId) {
24357
25102
  invalidateTrueRawTokenCache({ sessionId, reason: "pi.branch.changed" });
24358
25103
  activeContextHandlerSessions.delete(sessionId);
@@ -24411,6 +25156,14 @@ No active Pi session is available.`,
24411
25156
  });
24412
25157
  return;
24413
25158
  }
25159
+ if (deps.compactionOff) {
25160
+ sendCtxStatusMessage(pi, {
25161
+ title: "/ctx-flush",
25162
+ text: COMPACTION_OFF_COMMAND_UNAVAILABLE,
25163
+ level: "warning"
25164
+ });
25165
+ return;
25166
+ }
24414
25167
  const pendingBefore = getPendingOps(deps.db, sessionId).length;
24415
25168
  const result = executeFlush(deps.db, sessionId);
24416
25169
  signalPiHistoryRefresh(sessionId);
@@ -24455,6 +25208,24 @@ function historianResponseDumpDir(directory) {
24455
25208
  return getProjectMagicContextHistorianDir(directory);
24456
25209
  }
24457
25210
  var MAX_HISTORIAN_RETRIES2 = 2;
25211
+ var HISTORIAN_REASONING_PART_TYPES = new Set(["reasoning", "thinking", "redacted_thinking"]);
25212
+ function extractLatestHistorianReasoning(messages) {
25213
+ if (!Array.isArray(messages))
25214
+ return null;
25215
+ const latest = messages.filter((message) => isRecord(message) && isRecord(message.info) && message.info.role === "assistant").sort((left, right) => historianMessageCreatedAt(right) - historianMessageCreatedAt(left))[0];
25216
+ if (!latest || !Array.isArray(latest.parts))
25217
+ return null;
25218
+ return latest.parts.filter(isHistorianReasoningPart).map((part) => part.text).join(`
25219
+ `) || null;
25220
+ }
25221
+ function isHistorianReasoningPart(part) {
25222
+ return isRecord(part) && typeof part.type === "string" && HISTORIAN_REASONING_PART_TYPES.has(part.type) && typeof part.text === "string" && part.text.length > 0;
25223
+ }
25224
+ function historianMessageCreatedAt(message) {
25225
+ if (!isRecord(message.info) || !isRecord(message.info.time))
25226
+ return 0;
25227
+ return typeof message.info.time.created === "number" ? message.info.time.created : 0;
25228
+ }
24458
25229
  async function runValidatedHistorianPass(args) {
24459
25230
  const firstRun = await runHistorianPrompt({
24460
25231
  ...args,
@@ -24520,6 +25291,7 @@ async function runEditorPassOrFallback(args) {
24520
25291
  sessionLog(args.parentSessionId, "historian two-pass: running editor on draft");
24521
25292
  const editorRun = await runHistorianPrompt({
24522
25293
  client: args.client,
25294
+ db: args.db,
24523
25295
  parentSessionId: args.parentSessionId,
24524
25296
  sessionDirectory: args.sessionDirectory,
24525
25297
  prompt: buildHistorianEditorPrompt(args.draftXml),
@@ -24546,6 +25318,7 @@ async function runEditorPassOrFallback(args) {
24546
25318
  async function runHistorianPrompt(args) {
24547
25319
  const {
24548
25320
  client,
25321
+ db,
24549
25322
  parentSessionId,
24550
25323
  sessionDirectory,
24551
25324
  prompt,
@@ -24579,12 +25352,12 @@ async function runHistorianPrompt(args) {
24579
25352
  };
24580
25353
  try {
24581
25354
  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 }
25355
+ const createResponse = await createChildSessionWithFence({
25356
+ client,
25357
+ db,
25358
+ parentSessionId,
25359
+ title: "magic-context-compartment",
25360
+ directory: sessionDirectory
24588
25361
  });
24589
25362
  const createdSession = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
24590
25363
  agentSessionId = typeof createdSession?.id === "string" ? createdSession.id : null;
@@ -24632,7 +25405,18 @@ async function runHistorianPrompt(args) {
24632
25405
  preferResponseOnMissingData: true
24633
25406
  });
24634
25407
  const invocationId = recordInvocation5({ status: "completed", messages });
24635
- const result = extractLatestAssistantText(messages);
25408
+ const lengthCapped = hasLengthCappedOutput(messages);
25409
+ const textResult = extractLatestAssistantText(messages);
25410
+ const reasoningResult = textResult ? null : extractLatestHistorianReasoning(messages);
25411
+ if (!textResult && reasoningResult && lengthCapped) {
25412
+ const outputTokens = sumTokensFromChildMessages(messages).output;
25413
+ return {
25414
+ ok: false,
25415
+ 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`,
25416
+ invocationId: invocationId ?? undefined
25417
+ };
25418
+ }
25419
+ const result = textResult ?? reasoningResult;
24636
25420
  if (!result) {
24637
25421
  return {
24638
25422
  ok: false,
@@ -24684,6 +25468,7 @@ async function runFallbackHistorianPass(args) {
24684
25468
  args.callbacks?.onModelFallback?.(modelId, i + 1, chain.length);
24685
25469
  const fallbackRun = await runHistorianPrompt({
24686
25470
  client: args.client,
25471
+ db: args.db,
24687
25472
  parentSessionId: args.parentSessionId,
24688
25473
  sessionDirectory: args.sessionDirectory,
24689
25474
  prompt: args.prompt,
@@ -24779,116 +25564,6 @@ function sanitizeDumpName(value) {
24779
25564
  return value.replace(/[^a-zA-Z0-9._-]/g, "-");
24780
25565
  }
24781
25566
 
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
25567
  // ../plugin/src/hooks/magic-context/compartment-runner-incremental.ts
24893
25568
  var HISTORIAN_ALERT_COOLDOWN_MS2 = 60 * 1000;
24894
25569
  var lastHistorianAlertBySession2 = new Map;
@@ -25106,6 +25781,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
25106
25781
  emitProgress(`Running historian (pass ${passCount + 1})…`);
25107
25782
  const validatedPass = await runValidatedHistorianPass({
25108
25783
  client,
25784
+ db,
25109
25785
  parentSessionId: sessionId,
25110
25786
  sessionDirectory,
25111
25787
  prompt,
@@ -25525,6 +26201,7 @@ ${chunk.text}`,
25525
26201
  Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${chunk.startIndex}-${chunk.endIndex}.`, notifParams());
25526
26202
  const validatedPass = await runValidatedHistorianPass({
25527
26203
  client,
26204
+ db,
25528
26205
  parentSessionId: sessionId,
25529
26206
  sessionDirectory,
25530
26207
  prompt,
@@ -25880,8 +26557,8 @@ function renderStatusText(ctx, db, sessionId) {
25880
26557
  const inputTokens = typeof usage?.tokens === "number" ? usage.tokens : undefined;
25881
26558
  const pct = typeof usage?.percent === "number" ? usage.percent : undefined;
25882
26559
  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}`;
26560
+ const state2 = renderHistorianState(meta, recompSessions.has(sessionId));
26561
+ return `mc: ${inputTokens === undefined ? "--" : fmt(inputTokens)} (${pct === undefined ? "--" : `${Math.round(pct)}%`}) · ${state2}`;
25885
26562
  }
25886
26563
  function renderHistorianState(meta, recompActive) {
25887
26564
  const failureCount = meta?.historian_failure_count ?? 0;
@@ -25984,6 +26661,14 @@ No active Pi session is available.`,
25984
26661
  return;
25985
26662
  }
25986
26663
  const currentDeps = deps.resolveRuntimeDeps?.(ctx) ?? deps;
26664
+ if (currentDeps.compactionOff) {
26665
+ sendCtxStatusMessage(pi, {
26666
+ title: "/ctx-recomp",
26667
+ text: COMPACTION_OFF_COMMAND_UNAVAILABLE,
26668
+ level: "warning"
26669
+ });
26670
+ return;
26671
+ }
25987
26672
  const parsed = parseRecompArgs(args);
25988
26673
  if (parsed.kind === "error") {
25989
26674
  sendCtxStatusMessage(pi, {
@@ -26336,7 +27021,7 @@ function contextualizeUpgradeReason(reason) {
26336
27021
  // src/pi-memory-migration.ts
26337
27022
  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
27023
  async function runPiMemoryMigration(deps) {
26339
- const projectPath = resolveProjectIdentityForSession(deps.directory);
27024
+ const projectPath = resolveProjectIdentityForSession(deps.directory, deps.allowHomeProject);
26340
27025
  if (!projectPath) {
26341
27026
  return {
26342
27027
  ran: false,
@@ -26451,6 +27136,14 @@ No active Pi session is available.`,
26451
27136
  return;
26452
27137
  }
26453
27138
  const currentDeps = deps.resolveRuntimeDeps?.(ctx) ?? deps;
27139
+ if (currentDeps.compactionOff) {
27140
+ sendCtxStatusMessage(pi, {
27141
+ title: "/ctx-session-upgrade",
27142
+ text: COMPACTION_OFF_COMMAND_UNAVAILABLE,
27143
+ level: "warning"
27144
+ });
27145
+ return;
27146
+ }
26454
27147
  if (!currentDeps.historianModel) {
26455
27148
  sendCtxStatusMessage(pi, {
26456
27149
  title: "/ctx-session-upgrade",
@@ -26497,6 +27190,7 @@ An upgrade or recomp is already running for this session in the background. Wait
26497
27190
  timeoutMs: currentDeps.historianTimeoutMs,
26498
27191
  thinkingLevel: currentDeps.historianThinkingLevel,
26499
27192
  directory: ctx.cwd,
27193
+ allowHomeProject: currentDeps.allowHomeProject,
26500
27194
  sessionId,
26501
27195
  userMemoriesEnabled: currentDeps.userMemoriesEnabled,
26502
27196
  language: currentDeps.language
@@ -26507,7 +27201,7 @@ An upgrade or recomp is already running for this session in the background. Wait
26507
27201
  }
26508
27202
  };
26509
27203
  if (upgradableCount === 0) {
26510
- const projectPath = resolveProjectIdentityForSession(ctx.cwd);
27204
+ const projectPath = resolveProjectIdentityForSession(ctx.cwd, currentDeps.allowHomeProject);
26511
27205
  if (!projectPath)
26512
27206
  return;
26513
27207
  const migrationPending = migrationEnabled && !isMemoryMigrationDone(currentDeps.db, projectPath);
@@ -26683,7 +27377,7 @@ function formatExecuteThreshold(detail, contextLimit) {
26683
27377
  }
26684
27378
  return `${percentage}%${clampNote}`;
26685
27379
  }
26686
- function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageConfig = DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE, liveModelKey, historyBudgetPercentage, commitClusterTrigger, executeThresholdTokens, contextLimit) {
27380
+ function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageConfig = DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE, liveModelKey, historyBudgetPercentage, commitClusterTrigger, executeThresholdTokens, contextLimit, dreamer) {
26687
27381
  const thresholdDetail = resolveExecuteThresholdDetail(executeThresholdPercentageConfig, liveModelKey, DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE, {
26688
27382
  tokensConfig: executeThresholdTokens,
26689
27383
  contextLimit,
@@ -26728,8 +27422,8 @@ function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageC
26728
27422
  "### Cache TTL",
26729
27423
  `- Configured: ${meta.cacheTtl}`,
26730
27424
  `- 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}%`}`,
27425
+ `- Remaining: ${cacheExpired ? "expired" : ttlMs === Number.POSITIVE_INFINITY ? "never expires (always-warm lane)" : `${Math.round(remainingMs / 1000)}s`}`,
27426
+ `- Queue will auto-execute: ${cacheExpired ? "yes (cache expired)" : ttlMs === Number.POSITIVE_INFINITY ? `when context >= ${executeThresholdPercentage}%` : `when TTL expires or context >= ${executeThresholdPercentage}%`}`,
26733
27427
  "",
26734
27428
  "### Execute Threshold",
26735
27429
  `- Execute threshold: ${formatExecuteThreshold(thresholdDetail, displayContextLimit)}`,
@@ -26738,6 +27432,11 @@ function executeStatus(db, sessionId, protectedTags, executeThresholdPercentageC
26738
27432
  `**Protected tags:** ${protectedTags}`,
26739
27433
  `**Subagent session:** ${meta.isSubagent}`
26740
27434
  ];
27435
+ if (dreamer?.backlog && Object.keys(dreamer.backlog).length > 0) {
27436
+ lines.push("", "### Dreamer", ...dreamer.progress ? [
27437
+ `- Running: ${dreamer.progress.task} — ${dreamer.progress.processed}/${dreamer.progress.total} processed`
27438
+ ] : [], ...formatDreamTaskBacklogs(dreamer.backlog).split("\\n"));
27439
+ }
26741
27440
  if (meta.lastContextPercentage > 0 || meta.lastInputTokens > 0) {
26742
27441
  lines.push("", "### Context Usage", `- Last percentage: ${meta.lastContextPercentage.toFixed(1)}%`, `- Last input tokens: ${meta.lastInputTokens.toLocaleString()}`, `- Resolved context limit: ${displayContextLimit > 0 ? displayContextLimit.toLocaleString() : "unknown"}`, `- Proactive compartment evaluation: ${proactiveCompartmentTrigger}%`, `- Post-drop target for historian: ${(executeThresholdPercentage * POST_DROP_TARGET_RATIO).toFixed(0)}% (${executeThresholdPercentage}% * ${POST_DROP_TARGET_RATIO})`, `- Commit cluster trigger: ${commitClusterTrigger?.enabled !== false ? `enabled (min ${commitClusterTrigger?.min_clusters ?? 3} clusters)` : "disabled"}, tail-size trigger: > 3x compartment budget`);
26743
27442
  }
@@ -26760,6 +27459,12 @@ ${c.content}
26760
27459
  lines.push(`- §${op.tagId}§ → ${op.operation}`);
26761
27460
  }
26762
27461
  }
27462
+ if (dreamer?.backlog && Object.keys(dreamer.backlog).length > 0) {
27463
+ lines.push("", "### Dreamer Backlog", formatDreamTaskBacklogs(dreamer.backlog));
27464
+ }
27465
+ if (dreamer?.progress) {
27466
+ lines.push("", "### Dreamer Progress", `- ${dreamer.progress.task}: ${dreamer.progress.processed}/${dreamer.progress.total} processed this run`);
27467
+ }
26763
27468
  return lines.join(`
26764
27469
  `);
26765
27470
  } catch (error) {
@@ -26834,7 +27539,7 @@ ${c.content}
26834
27539
  // package.json
26835
27540
  var package_default = {
26836
27541
  name: "@cortexkit/pi-magic-context",
26837
- version: "0.33.1",
27542
+ version: "0.34.1",
26838
27543
  type: "module",
26839
27544
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
26840
27545
  main: "dist/index.js",
@@ -27004,7 +27709,7 @@ function renderInner(s, theme, innerWidth) {
27004
27709
  lines.push(`Upgrade: ${theme.fg("accent", "up to date")}`);
27005
27710
  }
27006
27711
  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`}`);
27712
+ 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
27713
  lines.push("");
27009
27714
  lines.push(theme.fg("muted", "Tags"));
27010
27715
  lines.push(`Active ${s.activeTags} (~${formatBytes(s.activeBytes)}) · Dropped ${s.droppedTags} · Total ${s.totalTags}`);
@@ -27046,8 +27751,18 @@ function buildPiStatusDetail(pi, ctx, deps, sessionId) {
27046
27751
  const usage = ctx.getContextUsage?.();
27047
27752
  const meta = getOrCreateSessionMeta(deps.db, sessionId);
27048
27753
  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;
27754
+ let detectedContextLimit;
27755
+ try {
27756
+ const detected = getOverflowState(deps.db, sessionId).detectedContextLimit;
27757
+ if (detected > 0)
27758
+ detectedContextLimit = detected;
27759
+ } catch {}
27760
+ const contextLimit = resolvePiUsableContextLimit({
27761
+ rawContextWindow: usage?.contextWindow ?? ctx.model?.contextWindow,
27762
+ model: ctx.model,
27763
+ detectedContextLimit
27764
+ }) ?? (meta.lastContextPercentage > 0 ? Math.round(inputTokens / (meta.lastContextPercentage / 100)) : 0);
27765
+ const usagePercentage = contextLimit > 0 && inputTokens > 0 ? inputTokens / contextLimit * 100 : meta.lastContextPercentage;
27051
27766
  const compartments = getCompartments(deps.db, sessionId);
27052
27767
  const metaRow = readSessionMetaRow(deps.db, sessionId);
27053
27768
  const memoryBlockCount = Number(metaRow?.memory_block_count ?? 0);
@@ -27095,9 +27810,15 @@ ${safeStringify(tool.parameters)}`);
27095
27810
  sessionId
27096
27811
  });
27097
27812
  const cacheTtl = meta.cacheTtl || "5m";
27098
- const cacheTtlMs = parseTtlString(cacheTtl);
27813
+ let cacheTtlMs;
27814
+ try {
27815
+ cacheTtlMs = parseCacheTtl(cacheTtl);
27816
+ } catch {
27817
+ cacheTtlMs = 5 * 60 * 1000;
27818
+ }
27819
+ const neverExpires = cacheTtlMs === Number.POSITIVE_INFINITY;
27099
27820
  const elapsed = meta.lastResponseTime > 0 ? Date.now() - meta.lastResponseTime : 0;
27100
- const cacheRemainingMs = meta.lastResponseTime > 0 ? Math.max(0, cacheTtlMs - elapsed) : cacheTtlMs;
27821
+ const cacheRemainingMs = neverExpires ? Number.POSITIVE_INFINITY : meta.lastResponseTime > 0 ? Math.max(0, cacheTtlMs - elapsed) : cacheTtlMs;
27101
27822
  const cacheExpired = meta.lastResponseTime > 0 && cacheRemainingMs === 0;
27102
27823
  const historyBlockTokens = compartmentTokens + factTokens;
27103
27824
  const historyBudgetPercentage = deps.historyBudgetPercentage ?? 0.15;
@@ -27252,22 +27973,6 @@ function readPendingOpsCount(db, sessionId) {
27252
27973
  return 0;
27253
27974
  }
27254
27975
  }
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
27976
  function safeRead(fn, fallback) {
27272
27977
  try {
27273
27978
  return fn();
@@ -27331,7 +28036,20 @@ No active Pi session is available.`,
27331
28036
  }
27332
28037
  const usage = ctx.getContextUsage?.();
27333
28038
  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);
28039
+ let detectedContextLimit;
28040
+ try {
28041
+ const detected = getOverflowState(currentDeps.db, sessionId).detectedContextLimit;
28042
+ if (detected > 0)
28043
+ detectedContextLimit = detected;
28044
+ } catch {}
28045
+ const usableContextLimit = resolvePiUsableContextLimit({
28046
+ rawContextWindow: usage?.contextWindow ?? ctx.model?.contextWindow,
28047
+ model: ctx.model,
28048
+ detectedContextLimit
28049
+ });
28050
+ const statusText = executeStatus(currentDeps.db, sessionId, currentDeps.protectedTags ?? 20, currentDeps.executeThresholdPercentage, modelKey, currentDeps.historyBudgetPercentage, currentDeps.commitClusterTrigger, currentDeps.executeThresholdTokens, usableContextLimit, {
28051
+ backlog: getDreamTaskBacklogs(currentDeps.db, currentDeps.projectIdentity, CANONICAL_DREAM_TASKS)
28052
+ });
27335
28053
  const details = buildStatusDetails(currentDeps, sessionId);
27336
28054
  sendCtxStatusMessage(pi, { title: "/ctx-status", text: statusText, level: "info" }, details);
27337
28055
  } catch (error) {
@@ -27373,6 +28091,7 @@ function buildStatusDetails(deps, sessionId) {
27373
28091
  dreamer: {
27374
28092
  enabled: deps.dreamer?.runnable === true,
27375
28093
  scheduleSummary: deps.dreamer?.scheduleSummary ?? null,
28094
+ backlog: getDreamTaskBacklogs(deps.db, deps.projectIdentity, CANONICAL_DREAM_TASKS),
27376
28095
  lastRunAt: getMostRecentTaskRunAt(deps.db, deps.projectIdentity)
27377
28096
  },
27378
28097
  historian: readHistorianState(deps.db, sessionId, meta)
@@ -27433,6 +28152,14 @@ No active Pi session is available.`,
27433
28152
  return;
27434
28153
  }
27435
28154
  const currentDeps = deps.resolveRuntimeDeps?.(ctx) ?? deps;
28155
+ if (currentDeps.compactionOff) {
28156
+ sendCtxStatusMessage(pi, {
28157
+ title: "/ctx-wrapup",
28158
+ text: COMPACTION_OFF_COMMAND_UNAVAILABLE,
28159
+ level: "warning"
28160
+ });
28161
+ return;
28162
+ }
27436
28163
  const sessionMeta = getOrCreateSessionMeta(currentDeps.db, sessionId);
27437
28164
  if (sessionMeta.isSubagent) {
27438
28165
  sendCtxStatusMessage(pi, {
@@ -27485,7 +28212,7 @@ async function runPiWrapup(pi, deps, ctx, sessionId, messagesToKeep) {
27485
28212
  const unregister = setRawMessageProvider(sessionId, provider);
27486
28213
  let holderId = "";
27487
28214
  try {
27488
- const contextLimit = resolvePiContextLimit(ctx);
28215
+ const contextLimit = resolvePiContextLimit(ctx, deps.db, sessionId);
27489
28216
  const modelKey = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
27490
28217
  const executeThresholdPercentage = resolveExecuteThreshold(deps.executeThresholdPercentage ?? 65, modelKey, 65, {
27491
28218
  tokensConfig: deps.executeThresholdTokens,
@@ -27714,15 +28441,19 @@ Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments.
27714
28441
  unregister();
27715
28442
  }
27716
28443
  }
27717
- function resolvePiContextLimit(ctx) {
28444
+ function resolvePiContextLimit(ctx, db, sessionId) {
27718
28445
  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;
28446
+ let detectedContextLimit;
28447
+ try {
28448
+ const detected = getOverflowState(db, sessionId).detectedContextLimit;
28449
+ if (detected > 0)
28450
+ detectedContextLimit = detected;
28451
+ } catch {}
28452
+ return resolvePiUsableContextLimit({
28453
+ rawContextWindow: usage?.contextWindow ?? ctx.model?.contextWindow,
28454
+ model: ctx.model,
28455
+ detectedContextLimit
28456
+ }) ?? 128000;
27726
28457
  }
27727
28458
  async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker, maxWaitMs) {
27728
28459
  const waitStartedAt = Date.now();
@@ -27742,15 +28473,15 @@ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarke
27742
28473
  function estimateChunks(tokens, chunkTokens) {
27743
28474
  return Math.max(1, Math.ceil(Math.max(0, tokens) / Math.max(1, chunkTokens)));
27744
28475
  }
27745
- function formatExistingWrapup(state) {
27746
- if (!state) {
28476
+ function formatExistingWrapup(state2) {
28477
+ if (!state2) {
27747
28478
  return `## Magic Wrapup — Skipped
27748
28479
 
27749
28480
  Another /ctx-wrapup is already compacting this session. Wait for it to finish, then try again.`;
27750
28481
  }
27751
28482
  return `## Magic Wrapup — Skipped
27752
28483
 
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.`;
28484
+ 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
28485
  }
27755
28486
  function readBranchEntries(ctx) {
27756
28487
  const getBranch = ctx.sessionManager.getBranch;
@@ -27874,15 +28605,43 @@ function stripTagPrefixFromAssistantMessage(message) {
27874
28605
 
27875
28606
  // src/index.ts
27876
28607
  var PREFIX2 = "[magic-context][pi]";
27877
- function resolveCurrentProject(ctx) {
28608
+ var PI_ACTIVE_LATCH = Symbol.for("magic-context.pi.active");
28609
+ function isPiMagicContextActiveInProcess() {
28610
+ return globalThis[PI_ACTIVE_LATCH] === true;
28611
+ }
28612
+ function markPiMagicContextActive() {
28613
+ globalThis[PI_ACTIVE_LATCH] = true;
28614
+ }
28615
+ function clearPiMagicContextActive() {
28616
+ try {
28617
+ delete globalThis[PI_ACTIVE_LATCH];
28618
+ } catch {
28619
+ globalThis[PI_ACTIVE_LATCH] = undefined;
28620
+ }
28621
+ }
28622
+ function resolveCurrentProject(ctx, allowHomeProject = false) {
27878
28623
  const projectDir = ctx.cwd;
27879
- const projectIdentity = resolveProjectIdentityForSession(projectDir) ?? "";
28624
+ const projectIdentity = resolveProjectIdentityForSession(projectDir, allowHomeProject) ?? "";
27880
28625
  return { projectDir, projectIdentity };
27881
28626
  }
27882
28627
  function signalPiDeferredCompactionMarkerDrain(sessionId) {
27883
28628
  signalPiDeferredHistoryRefresh(sessionId);
27884
28629
  signalPiDeferredMaterialization(sessionId);
27885
28630
  }
28631
+ async function handlePiSessionBeforeCompact(args) {
28632
+ try {
28633
+ const sessionId = args.ctx.sessionManager?.getSessionId?.();
28634
+ if (typeof sessionId === "string" && sessionId.length > 0) {
28635
+ clearPiM0Cache(args.db, sessionId, "session_before_compact");
28636
+ }
28637
+ } catch {}
28638
+ if (args.compactionOff) {
28639
+ info("session_before_compact: native Pi compaction proceeds (compaction-off mode)");
28640
+ return;
28641
+ }
28642
+ info("session_before_compact: cancelling — magic-context owns compaction");
28643
+ return { cancel: true };
28644
+ }
27886
28645
  function persistPiMessageEndModelMeta(args) {
27887
28646
  if (!args.message || typeof args.message !== "object")
27888
28647
  return;
@@ -27988,7 +28747,10 @@ var __test = {
27988
28747
  logPiConfigLoad,
27989
28748
  resetLoggedPiConfigDirs() {
27990
28749
  loggedPiConfigDirs.clear();
27991
- }
28750
+ },
28751
+ isPiMagicContextActiveInProcess,
28752
+ markPiMagicContextActive,
28753
+ clearPiMagicContextActive
27992
28754
  };
27993
28755
  function formatTokens(value) {
27994
28756
  return value.toLocaleString();
@@ -28004,23 +28766,28 @@ function getPiMessageModel(message) {
28004
28766
  };
28005
28767
  }
28006
28768
  function resolvePiPressureContextLimit(args) {
28007
- let effectiveContextLimit = isSaneLimit(args.piContextWindow) ? args.piContextWindow : 0;
28769
+ let detectedContextLimit;
28008
28770
  try {
28009
28771
  const overflowState = getOverflowState(args.db, args.sessionId);
28010
28772
  if (overflowState.detectedContextLimit > 0) {
28011
- effectiveContextLimit = effectiveContextLimit > 0 ? Math.min(effectiveContextLimit, overflowState.detectedContextLimit) : overflowState.detectedContextLimit;
28773
+ detectedContextLimit = overflowState.detectedContextLimit;
28012
28774
  }
28013
28775
  } catch (err) {
28014
28776
  warn("message_end: getOverflowState failed:", err);
28015
28777
  }
28016
- return effectiveContextLimit;
28778
+ return resolvePiUsableContextLimit({
28779
+ rawContextWindow: args.piContextWindow,
28780
+ model: args.model,
28781
+ detectedContextLimit
28782
+ }) ?? 0;
28017
28783
  }
28018
28784
  async function persistPiPressureFromMessageEnd(args) {
28019
28785
  const { provider, model } = getPiMessageModel(args.message);
28020
28786
  const effectiveContextLimit = resolvePiPressureContextLimit({
28021
28787
  db: args.db,
28022
28788
  sessionId: args.sessionId,
28023
- piContextWindow: args.piContextWindow
28789
+ piContextWindow: args.piContextWindow,
28790
+ model: args.piModel ?? { provider, id: model }
28024
28791
  });
28025
28792
  const usage = extractAssistantUsage(args.message);
28026
28793
  const pressure = computePiPressure(usage, effectiveContextLimit);
@@ -28047,8 +28814,8 @@ async function persistPiPressureFromMessageEnd(args) {
28047
28814
  }
28048
28815
  } else if (typeof args.piTokens === "number") {
28049
28816
  updates.lastInputTokens = args.piTokens;
28050
- if (args.piContextWindow > 0) {
28051
- updates.lastContextPercentage = args.piTokens / args.piContextWindow * 100;
28817
+ if (effectiveContextLimit > 0) {
28818
+ updates.lastContextPercentage = args.piTokens / effectiveContextLimit * 100;
28052
28819
  }
28053
28820
  }
28054
28821
  updateSessionMeta(args.db, args.sessionId, updates);
@@ -28075,7 +28842,8 @@ function resolveSidekickFromConfig(config) {
28075
28842
  timeoutMs: sidekick.timeout_ms,
28076
28843
  thinking_level: sidekick.thinking_level,
28077
28844
  fallbackModels: resolveFallbackChain(sidekick.fallback_models),
28078
- language: config.language
28845
+ language: config.language,
28846
+ allowHomeProject: config.allow_home_project
28079
28847
  };
28080
28848
  }
28081
28849
  function resolveHistorianFromConfig(config) {
@@ -28105,7 +28873,8 @@ function resolveHistorianFromConfig(config) {
28105
28873
  memoryEnabled: config.memory.enabled,
28106
28874
  autoPromote: config.memory.auto_promote,
28107
28875
  userMemoriesEnabled: userMemoryCollectionEnabled(config.dreamer),
28108
- language: config.language
28876
+ language: config.language,
28877
+ allowHomeProject: config.allow_home_project
28109
28878
  };
28110
28879
  }
28111
28880
  function resolveAutoSearchFromConfig(config) {
@@ -28125,7 +28894,20 @@ async function src_default(pi) {
28125
28894
  log(`${PREFIX2} subagent child detected (${MAGIC_CONTEXT_PI_SUBAGENT_ENV}=1); skipping full extension registration`);
28126
28895
  return;
28127
28896
  }
28897
+ if (isPiMagicContextActiveInProcess()) {
28898
+ log(`${PREFIX2} in-process re-init detected (Magic Context already active in this process); skipping full extension registration`);
28899
+ return;
28900
+ }
28901
+ markPiMagicContextActive();
28128
28902
  beginBootQuietPeriod();
28903
+ const bootProjectDir = process.cwd();
28904
+ ensureConfigLocationsMigrated(bootProjectDir);
28905
+ const bootConfig = loadPiConfig({ cwd: bootProjectDir });
28906
+ setStoragePrivatePermissionEnforcement(bootConfig.config.storage.enforce_private_permissions);
28907
+ setSqlitePragmaConfig({
28908
+ cacheSizeMb: bootConfig.config.sqlite.cache_size_mb,
28909
+ mmapSizeMb: bootConfig.config.sqlite.mmap_size_mb
28910
+ });
28129
28911
  const storageDir = getMagicContextStorageDir();
28130
28912
  const dbPath = join5(storageDir, "context.db");
28131
28913
  let db;
@@ -28144,16 +28926,26 @@ async function src_default(pi) {
28144
28926
  info("plugin DISABLED via config (enabled: false) — skipping registration");
28145
28927
  return;
28146
28928
  }
28929
+ const migration = getMigrationOnOpenRefusal();
28930
+ const blockingProcesses = migration?.serverPids.map((pid) => ({
28931
+ harness: "OpenCode server",
28932
+ pid
28933
+ })) ?? [];
28147
28934
  const fence = getSchemaFenceRejection();
28148
- const reason = fence ? {
28935
+ const reason = migration && blockingProcesses.length > 0 ? {
28936
+ kind: "migration_guard",
28937
+ persistedVersion: migration.persistedVersion,
28938
+ supportedVersion: migration.supportedVersion,
28939
+ blockingProcesses
28940
+ } : fence ? {
28149
28941
  kind: "schema_fence",
28150
28942
  persistedVersion: fence.persistedVersion,
28151
28943
  supportedVersion: fence.supportedVersion
28152
28944
  } : {
28153
28945
  kind: "storage_failure",
28154
- cause: openFailureCause ?? `storage unavailable at ${dbPath} (cache schema newer than this binary, or open failed)`
28946
+ 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
28947
  };
28156
- if (early.config.fail_closed_blocking === false) {
28948
+ if (early.config.fail_closed_blocking === false || !isCompactionEnabled(early.config)) {
28157
28949
  warn(`Magic Context (pi) storage unavailable at ${dbPath}: ${formatFailClosedBlockingMessage(reason)}. ` + "fail_closed_blocking=false — degrading silently (hooks not registered).");
28158
28950
  return;
28159
28951
  }
@@ -28201,26 +28993,30 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28201
28993
  })();
28202
28994
  }, 0);
28203
28995
  const projectDir = process.cwd();
28204
- const projectIdentity = resolveProjectIdentityForSession(projectDir) ?? "";
28205
28996
  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
28997
  ensureConfigLocationsMigrated(projectDir);
28221
28998
  const { config, warnings, loadedFromPaths } = loadPiConfig({
28222
28999
  cwd: projectDir
28223
29000
  });
29001
+ const projectIdentity = resolveProjectIdentityForSession(projectDir, config.allow_home_project) ?? "";
29002
+ if (projectIdentity)
29003
+ seenDreamerProjectIdentities.add(projectIdentity);
29004
+ info(`loaded v${PLUGIN_VERSION} | harness=pi | db=${dbPath} | ` + `project=${projectIdentity} | dir=${projectDir}`);
29005
+ const compactionOff = !isCompactionEnabled(config);
29006
+ setCtxReduceRegisteredGlobally(!compactionOff);
29007
+ if (!compactionOff) {
29008
+ try {
29009
+ const pendingPiMarkerSessions = getSessionsWithPendingPiMarker(db);
29010
+ for (const sid of pendingPiMarkerSessions) {
29011
+ signalPiDeferredCompactionMarkerDrain(sid);
29012
+ }
29013
+ if (pendingPiMarkerSessions.length > 0) {
29014
+ log(`${PREFIX2} rehydrated ${pendingPiMarkerSessions.length} Pi deferred compaction marker session(s)`);
29015
+ }
29016
+ } catch (err) {
29017
+ warn(`Magic Context (pi) failed to rehydrate deferred Pi compaction markers: ${err instanceof Error ? err.message : String(err)}`);
29018
+ }
29019
+ }
28224
29020
  configurePiSubagentExtensions(config.pi?.subagent_extensions);
28225
29021
  logPiConfigLoad({
28226
29022
  dir: projectDir,
@@ -28228,6 +29024,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28228
29024
  warnings,
28229
29025
  dedupe: true
28230
29026
  });
29027
+ setStoragePrivatePermissionEnforcement(config.storage.enforce_private_permissions);
28231
29028
  setSqlitePragmaConfig({
28232
29029
  cacheSizeMb: config.sqlite.cache_size_mb,
28233
29030
  mmapSizeMb: config.sqlite.mmap_size_mb
@@ -28257,7 +29054,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28257
29054
  injectDocs: cfg.dreamer?.inject_docs !== false,
28258
29055
  injectionBudgetTokens: cfg.memory.injection_budget_tokens,
28259
29056
  temporalAwareness: cfg.temporal_awareness === true,
28260
- muralEnabled: cfg.experimental?.mural?.enabled === true
29057
+ muralEnabled: cfg.mural.enabled === true
28261
29058
  },
28262
29059
  scheduler: {
28263
29060
  executeThresholdPercentage: cfg.execute_threshold_percentage,
@@ -28267,6 +29064,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28267
29064
  language: cfg.language,
28268
29065
  autoSearch: auto,
28269
29066
  resolveForProject: resolveContextOptionsForProject,
29067
+ compactionOff,
29068
+ allowHomeProject: cfg.allow_home_project,
28270
29069
  maybeAutoEmbedSession: (sessionId, dir, identity) => {
28271
29070
  maybeAutoEmbedPiSession({
28272
29071
  db: database,
@@ -28288,7 +29087,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28288
29087
  hist.onStatusChange = (ctx) => {
28289
29088
  updateStatusLine(ctx, {
28290
29089
  db: database,
28291
- projectIdentity: resolveCurrentProject(ctx).projectIdentity ?? ""
29090
+ projectIdentity: resolveCurrentProject(ctx, cfg.allow_home_project).projectIdentity ?? ""
28292
29091
  });
28293
29092
  };
28294
29093
  }
@@ -28318,14 +29117,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28318
29117
  dedupe: true
28319
29118
  });
28320
29119
  const switchedConfig = switchedLoad.config;
28321
- const switchedIdentity = identityOverride ?? resolveProjectIdentityForSession(dir) ?? "";
29120
+ const switchedIdentity = identityOverride ?? resolveProjectIdentityForSession(dir, switchedConfig.allow_home_project) ?? "";
28322
29121
  const built = buildProjectDeps(dir, switchedIdentity, switchedConfig);
28323
29122
  projectDepsByDir.set(dir, built);
28324
29123
  return built;
28325
29124
  }
28326
29125
  function resolveCurrentProjectDeps(ctx) {
28327
- const currentProject = resolveCurrentProject(ctx);
28328
- return resolveProjectDepsForDir(currentProject.projectDir, currentProject.projectIdentity);
29126
+ return resolveProjectDepsForDir(ctx.cwd);
28329
29127
  }
28330
29128
  function resolveContextOptionsForProject(dir) {
28331
29129
  return resolveProjectDepsForDir(dir).contextOptions;
@@ -28341,11 +29139,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28341
29139
  memoryToolEnabled: true,
28342
29140
  protectedTags: config.protected_tags ?? 20,
28343
29141
  resolveProtectedTags: (ctx) => resolveCurrentProjectDeps(ctx).config.protected_tags ?? 20,
29142
+ resolveProjectIdentity: (ctx) => resolveCurrentProjectDeps(ctx).projectIdentity,
28344
29143
  dreamerEnabled: isDreamerRunnable(config),
28345
29144
  resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
28346
- todowriteEnabled
29145
+ todowriteEnabled,
29146
+ compactionOff
28347
29147
  });
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)");
29148
+ 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
29149
  pi.on("session_start", async (event, ctx) => {
28350
29150
  await handlePiCloneSessionStart(event, ctx, {
28351
29151
  db,
@@ -28389,7 +29189,6 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28389
29189
  return {
28390
29190
  db,
28391
29191
  projectIdentity: current.projectIdentity,
28392
- resolveProject: resolveCurrentProject,
28393
29192
  protectedTags: current.config.protected_tags,
28394
29193
  executeThresholdPercentage: current.config.execute_threshold_percentage,
28395
29194
  historyBudgetPercentage: current.config.history_budget_percentage,
@@ -28406,7 +29205,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28406
29205
  info("registered /ctx-status");
28407
29206
  registerStatusLine(pi, { db, projectIdentity });
28408
29207
  info("registered magic-context status line");
28409
- registerCtxFlushCommand(pi, { db });
29208
+ registerCtxFlushCommand(pi, { db, compactionOff });
28410
29209
  info("registered /ctx-flush");
28411
29210
  registerCtxRecompCommand(pi, {
28412
29211
  db,
@@ -28419,6 +29218,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28419
29218
  language: bootProjectDeps.config.language,
28420
29219
  memoryEnabled: bootProjectDeps.config.memory.enabled,
28421
29220
  autoPromote: bootProjectDeps.config.memory.auto_promote,
29221
+ compactionOff,
28422
29222
  resolveRuntimeDeps: (ctx) => {
28423
29223
  const current = resolveCurrentProjectDeps(ctx);
28424
29224
  return {
@@ -28431,7 +29231,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28431
29231
  historianThinkingLevel: current.historianConfig?.thinkingLevel,
28432
29232
  language: current.config.language,
28433
29233
  memoryEnabled: current.config.memory.enabled,
28434
- autoPromote: current.config.memory.auto_promote
29234
+ autoPromote: current.config.memory.auto_promote,
29235
+ compactionOff
28435
29236
  };
28436
29237
  }
28437
29238
  });
@@ -28447,6 +29248,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28447
29248
  language: bootProjectDeps.config.language,
28448
29249
  memoryEnabled: bootProjectDeps.config.memory.enabled,
28449
29250
  autoPromote: bootProjectDeps.config.memory.auto_promote,
29251
+ compactionOff,
28450
29252
  userMemoriesEnabled: userMemoryCollectionEnabled(bootProjectDeps.config.dreamer),
28451
29253
  executeThresholdPercentage: bootProjectDeps.config.execute_threshold_percentage,
28452
29254
  executeThresholdTokens: bootProjectDeps.config.execute_threshold_tokens,
@@ -28463,6 +29265,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28463
29265
  language: current.config.language,
28464
29266
  memoryEnabled: current.config.memory.enabled,
28465
29267
  autoPromote: current.config.memory.auto_promote,
29268
+ compactionOff,
28466
29269
  userMemoriesEnabled: userMemoryCollectionEnabled(current.config.dreamer),
28467
29270
  executeThresholdPercentage: current.config.execute_threshold_percentage,
28468
29271
  executeThresholdTokens: current.config.execute_threshold_tokens
@@ -28480,7 +29283,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28480
29283
  historianThinkingLevel: bootProjectDeps.historianConfig?.thinkingLevel,
28481
29284
  language: bootProjectDeps.config.language,
28482
29285
  memoryEnabled: bootProjectDeps.config.memory.enabled,
29286
+ allowHomeProject: bootProjectDeps.config.allow_home_project,
28483
29287
  autoPromote: bootProjectDeps.config.memory.auto_promote,
29288
+ compactionOff,
28484
29289
  userMemoriesEnabled: userMemoryCollectionEnabled(bootProjectDeps.config.dreamer),
28485
29290
  resolveRuntimeDeps: (ctx) => {
28486
29291
  const current = resolveCurrentProjectDeps(ctx);
@@ -28494,7 +29299,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28494
29299
  historianThinkingLevel: current.historianConfig?.thinkingLevel,
28495
29300
  language: current.config.language,
28496
29301
  memoryEnabled: current.config.memory.enabled,
29302
+ allowHomeProject: current.config.allow_home_project,
28497
29303
  autoPromote: current.config.memory.auto_promote,
29304
+ compactionOff,
28498
29305
  userMemoriesEnabled: userMemoryCollectionEnabled(current.config.dreamer)
28499
29306
  };
28500
29307
  }
@@ -28504,7 +29311,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28504
29311
  db,
28505
29312
  projectDir,
28506
29313
  projectIdentity,
28507
- resolveProject: resolveCurrentProject,
29314
+ resolveProject: (ctx) => {
29315
+ const current = resolveCurrentProjectDeps(ctx);
29316
+ return {
29317
+ projectDir: current.projectDir,
29318
+ projectIdentity: current.projectIdentity
29319
+ };
29320
+ },
28508
29321
  dreamerEnabled: bootProjectDeps.dreamerEnabled,
28509
29322
  resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
28510
29323
  onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity)
@@ -28516,7 +29329,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28516
29329
  projectIdentity,
28517
29330
  memoryEnabled: bootProjectDeps.config.memory.enabled,
28518
29331
  resolveMemoryEnabled: (ctx) => resolveCurrentProjectDeps(ctx).config.memory.enabled,
28519
- resolveProject: resolveCurrentProject
29332
+ resolveProject: (ctx) => {
29333
+ const current = resolveCurrentProjectDeps(ctx);
29334
+ return {
29335
+ projectDir: current.projectDir,
29336
+ projectIdentity: current.projectIdentity
29337
+ };
29338
+ }
28520
29339
  });
28521
29340
  info("registered /ctx-embed");
28522
29341
  const dreamerConfig = bootProjectDeps.dreamerConfig;
@@ -28605,11 +29424,11 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28605
29424
  try {
28606
29425
  const smForDrain = sm;
28607
29426
  const canDrain = typeof smForDrain.appendCompaction === "function" && typeof smForDrain.getBranch === "function";
28608
- if (canDrain && getPendingPiCompactionMarkerState(db, sessionId)) {
29427
+ if (!compactionOff && canDrain && getPendingPiCompactionMarkerState(db, sessionId)) {
28609
29428
  signalPiDeferredCompactionMarkerDrain(sessionId);
28610
29429
  }
28611
29430
  } catch {}
28612
- if (ctx.hasUI && effectiveProjectDeps.historianConfig?.model) {
29431
+ if (!compactionOff && ctx.hasUI && effectiveProjectDeps.historianConfig?.model) {
28613
29432
  maybeSendUpgradeReminder({
28614
29433
  client: null,
28615
29434
  db,
@@ -28637,7 +29456,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
28637
29456
  memoryEnabled: effectiveConfig.memory.enabled,
28638
29457
  includeGuidance: true,
28639
29458
  protectedTags: effectiveConfig.protected_tags,
28640
- ctxReduceCallable: true,
29459
+ ctxReduceCallable: !compactionOff,
28641
29460
  dreamerEnabled: effectiveProjectDeps.dreamerEnabled,
28642
29461
  temporalAwarenessEnabled: effectiveConfig.temporal_awareness ?? false,
28643
29462
  cavemanTextCompressionEnabled: effectiveConfig.caveman_text_compression?.enabled === true,
@@ -28682,7 +29501,7 @@ ${block}` : event.systemPrompt;
28682
29501
  const lastAssistant = Array.isArray(msgs) ? [...msgs].reverse().find((m) => m?.role === "assistant") : undefined;
28683
29502
  if (lastAssistant?.stopReason === "stop") {
28684
29503
  const sessionId = ctx.sessionManager?.getSessionId?.();
28685
- if (sessionId && db)
29504
+ if (sessionId && db && !compactionOff)
28686
29505
  maybeDeliverChannel2Pi(pi, db, sessionId);
28687
29506
  }
28688
29507
  } catch (err) {
@@ -28707,7 +29526,7 @@ ${block}` : event.systemPrompt;
28707
29526
  toolCallId
28708
29527
  });
28709
29528
  if (Array.isArray(todos) && todos.length > 0 && todos.every((t) => t.status === "completed" || t.status === "cancelled")) {
28710
- if (sessionMeta && !sessionMeta.isSubagent) {
29529
+ if (!compactionOff && sessionMeta && !sessionMeta.isSubagent) {
28711
29530
  onNoteTrigger(db, sessionId, "todos_complete");
28712
29531
  }
28713
29532
  }
@@ -28723,7 +29542,7 @@ ${block}` : event.systemPrompt;
28723
29542
  const sessionId = ctx.sessionManager.getSessionId();
28724
29543
  if (typeof sessionId !== "string" || sessionId.length === 0)
28725
29544
  return;
28726
- if (event.toolName === "ctx_reduce") {
29545
+ if (!compactionOff && event.toolName === "ctx_reduce") {
28727
29546
  markPiChannel1Reduced(sessionId, db);
28728
29547
  }
28729
29548
  } catch (err) {
@@ -28735,6 +29554,8 @@ ${block}` : event.systemPrompt;
28735
29554
  const sessionId = ctx.sessionManager.getSessionId();
28736
29555
  if (typeof sessionId !== "string" || sessionId.length === 0)
28737
29556
  return;
29557
+ if (compactionOff)
29558
+ return;
28738
29559
  if (db)
28739
29560
  maybeDeliverChannel2Pi(pi, db, sessionId, "steer");
28740
29561
  const block = maybeChannel1ReminderForToolResult({
@@ -28750,20 +29571,11 @@ ${block}` : event.systemPrompt;
28750
29571
  log(`tool_result hook failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
28751
29572
  }
28752
29573
  });
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
- });
29574
+ pi.on("session_before_compact", async (_event, ctx) => handlePiSessionBeforeCompact({ db, compactionOff, ctx }));
28763
29575
  pi.on("message_end", async (event, ctx) => {
28764
29576
  try {
28765
29577
  const msg = event.message;
28766
- if (msg !== null && typeof msg === "object") {
29578
+ if (!compactionOff && msg !== null && typeof msg === "object") {
28767
29579
  stripTagPrefixFromAssistantMessage(msg);
28768
29580
  }
28769
29581
  } catch (err) {
@@ -28795,6 +29607,7 @@ ${block}` : event.systemPrompt;
28795
29607
  sessionId,
28796
29608
  message: event.message,
28797
29609
  piContextWindow,
29610
+ piModel: ctx.model,
28798
29611
  piTokens: piUsage && typeof piUsage.tokens === "number" ? piUsage.tokens : undefined,
28799
29612
  notifyIssue: async (message) => {
28800
29613
  const uiNotify = ctx.ui?.notify;
@@ -28824,6 +29637,8 @@ ${block}` : event.systemPrompt;
28824
29637
  warn("message_end: persist session_meta usage failed:", err);
28825
29638
  }
28826
29639
  try {
29640
+ if (compactionOff)
29641
+ return;
28827
29642
  const sm = ctx.sessionManager;
28828
29643
  const sessionId = sm?.getSessionId?.();
28829
29644
  if (typeof sessionId !== "string" || sessionId.length === 0)
@@ -28841,8 +29656,8 @@ ${block}` : event.systemPrompt;
28841
29656
  if (!detection.isOverflow)
28842
29657
  return;
28843
29658
  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 ?? "?"}`);
29659
+ recordOverflowDetected(db, sessionId, detection.reportedLimit, modelKey, "provider_overflow", detection.reportedLimitProvenance);
29660
+ log(`[magic-context][${sessionId}] overflow detected: reportedLimit=${detection.reportedLimit ?? "?"} provenance=${detection.reportedLimitProvenance ?? "?"} pattern=${detection.matchedPattern ?? "?"}`);
28846
29661
  } catch (err) {
28847
29662
  warn("message_end: overflow detection failed:", err);
28848
29663
  }
@@ -28879,6 +29694,7 @@ ${block}` : event.systemPrompt;
28879
29694
  clearContextHandlerSession(sessionId);
28880
29695
  }
28881
29696
  } catch {}
29697
+ clearPiMagicContextActive();
28882
29698
  });
28883
29699
  pi.on("session_before_switch", (_event, ctx) => {
28884
29700
  try {
@@ -28907,6 +29723,7 @@ export {
28907
29723
  resolveDreamerFromConfig,
28908
29724
  persistPiPressureFromMessageEnd,
28909
29725
  persistPiMessageEndModelMeta,
29726
+ handlePiSessionBeforeCompact,
28910
29727
  src_default as default,
28911
29728
  capturePiTodowriteMessageIfCompatible,
28912
29729
  capturePiTodowriteArgsIfCompatible,