@cortexkit/opencode-magic-context 0.41.0 → 0.41.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/dist/config/schema/magic-context.d.ts +15 -0
  2. package/dist/config/schema/magic-context.d.ts.map +1 -1
  3. package/dist/{conflict-warning-hook-xj1r88a7.js → conflict-warning-hook-rf17b6et.js} +3 -3
  4. package/dist/{derive-budgets-3xfyphvk.js → derive-budgets-9q2d3r93.js} +2 -2
  5. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +16 -17
  6. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
  7. package/dist/features/magic-context/compartment-embedding.d.ts.map +1 -1
  8. package/dist/features/magic-context/context-authority.d.ts.map +1 -1
  9. package/dist/features/magic-context/dreamer/curate-memory-safety.d.ts +21 -0
  10. package/dist/features/magic-context/dreamer/curate-memory-safety.d.ts.map +1 -0
  11. package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
  12. package/dist/features/magic-context/dreamer/task-prompts.d.ts +1 -6
  13. package/dist/features/magic-context/dreamer/task-prompts.d.ts.map +1 -1
  14. package/dist/features/magic-context/memory/embedding-failure.d.ts +10 -2
  15. package/dist/features/magic-context/memory/embedding-failure.d.ts.map +1 -1
  16. package/dist/features/magic-context/memory/embedding-local.d.ts +19 -2
  17. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  18. package/dist/features/magic-context/memory/embedding-synapse.d.ts +10 -1
  19. package/dist/features/magic-context/memory/embedding-synapse.d.ts.map +1 -1
  20. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  21. package/dist/features/magic-context/memory/onnxruntime-node-wasm-shim.d.ts +3 -0
  22. package/dist/features/magic-context/memory/onnxruntime-node-wasm-shim.d.ts.map +1 -0
  23. package/dist/features/magic-context/memory/project-identity.d.ts +4 -0
  24. package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
  25. package/dist/features/magic-context/memory/sharp-wasm-shim.d.ts +2 -0
  26. package/dist/features/magic-context/memory/sharp-wasm-shim.d.ts.map +1 -0
  27. package/dist/features/magic-context/memory/transformers-node-wasm-entry.d.ts +3 -0
  28. package/dist/features/magic-context/memory/transformers-node-wasm-entry.d.ts.map +1 -0
  29. package/dist/features/magic-context/memory/transformers-web-entry.d.ts +2 -0
  30. package/dist/features/magic-context/memory/transformers-web-entry.d.ts.map +1 -0
  31. package/dist/features/magic-context/message-fts-rowid-map.d.ts +28 -0
  32. package/dist/features/magic-context/message-fts-rowid-map.d.ts.map +1 -0
  33. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  34. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  35. package/dist/features/magic-context/overflow-detection.d.ts +15 -2
  36. package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
  37. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  38. package/dist/features/magic-context/search.d.ts +18 -0
  39. package/dist/features/magic-context/search.d.ts.map +1 -1
  40. package/dist/features/magic-context/storage-db.d.ts +2 -1
  41. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  42. package/dist/features/magic-context/storage-meta-persisted.d.ts +19 -2
  43. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  44. package/dist/features/magic-context/storage-meta-session.d.ts +3 -2
  45. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  46. package/dist/features/magic-context/storage-meta.d.ts +2 -2
  47. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  48. package/dist/features/magic-context/storage-ops.d.ts +2 -0
  49. package/dist/features/magic-context/storage-ops.d.ts.map +1 -1
  50. package/dist/features/magic-context/storage-schema-helpers.d.ts.map +1 -1
  51. package/dist/features/magic-context/storage-session-tables.d.ts +9 -1
  52. package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
  53. package/dist/features/magic-context/storage-tags.d.ts +13 -0
  54. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  55. package/dist/features/magic-context/storage.d.ts +2 -2
  56. package/dist/features/magic-context/storage.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/boundary-execution.d.ts +3 -0
  58. package/dist/hooks/magic-context/boundary-execution.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/event-handler.d.ts +4 -1
  60. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  61. package/dist/hooks/magic-context/format-embed-failure.d.ts.map +1 -1
  62. package/dist/hooks/magic-context/hook-handlers.d.ts +3 -0
  63. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  64. package/dist/hooks/magic-context/hook.d.ts +1 -1
  65. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  66. package/dist/hooks/magic-context/live-session-state.d.ts +3 -1
  67. package/dist/hooks/magic-context/live-session-state.d.ts.map +1 -1
  68. package/dist/hooks/magic-context/rust-mode-transform.d.ts +9 -1
  69. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  70. package/dist/hooks/magic-context/sentinel.d.ts +13 -37
  71. package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
  72. package/dist/hooks/magic-context/strip-content.d.ts +12 -5
  73. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  74. package/dist/hooks/magic-context/tag-id-fallback.d.ts +3 -1
  75. package/dist/hooks/magic-context/tag-id-fallback.d.ts.map +1 -1
  76. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  77. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +28 -2
  78. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  79. package/dist/hooks/magic-context/transform.d.ts +1 -1
  80. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  81. package/dist/{index-b4gv9f7g.js → index-4x691bz5.js} +87 -2
  82. package/dist/{index-knna83k0.js → index-9j3tx8hy.js} +1 -1
  83. package/dist/{index-jmwq2wxr.js → index-b9j85d29.js} +1 -1
  84. package/dist/{index-2bykz3bx.js → index-cvda5jb3.js} +7 -2
  85. package/dist/{index-bf799gbq.js → index-d1mxrd3p.js} +0 -1
  86. package/dist/{index-dmgbar7r.js → index-eq9wr75c.js} +2 -2
  87. package/dist/{index-w8rkbhn6.js → index-j8r0ejb3.js} +1232 -261
  88. package/dist/{index-26ds03jd.js → index-vnz6jvdc.js} +1 -1
  89. package/dist/{index-x9dmzyp5.js → index-y1yr57fw.js} +81 -17
  90. package/dist/{index-88za3sz8.js → index-zwjhba2r.js} +3 -3
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +1771 -1109
  93. package/dist/{model-resolution-jgrkt0z2.js → model-resolution-6w6wzw18.js} +1 -1
  94. package/dist/plugin/dream-timer.d.ts +4 -0
  95. package/dist/plugin/dream-timer.d.ts.map +1 -1
  96. package/dist/plugin/embedding-routing.d.ts.map +1 -1
  97. package/dist/plugin/hooks/create-session-hooks.d.ts +2 -2
  98. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  99. package/dist/{recomp-orchestrator-1w4xj6jt.js → recomp-orchestrator-m37p3fq6.js} +6 -6
  100. package/dist/{safe-notification-target-p0c0csxw.js → safe-notification-target-2a8py95v.js} +2 -2
  101. package/dist/{send-session-notification-kn34g7mx.js → send-session-notification-bw7eex6w.js} +3 -3
  102. package/dist/shared/index.d.ts +1 -0
  103. package/dist/shared/index.d.ts.map +1 -1
  104. package/dist/shared/logger.d.ts.map +1 -1
  105. package/dist/shared/model-resolution.d.ts.map +1 -1
  106. package/dist/shared/rpc-utils.d.ts +3 -1
  107. package/dist/shared/rpc-utils.d.ts.map +1 -1
  108. package/dist/shared/summarize-child-stderr.d.ts +2 -0
  109. package/dist/shared/summarize-child-stderr.d.ts.map +1 -0
  110. package/dist/shared/test-temp-dir.d.ts +40 -0
  111. package/dist/shared/test-temp-dir.d.ts.map +1 -0
  112. package/dist/shared/window-geometry.d.ts +2 -0
  113. package/dist/shared/window-geometry.d.ts.map +1 -1
  114. package/dist/{storage-meta-session-snbzd26z.js → storage-meta-session-dse3qv6s.js} +5 -3
  115. package/dist/{task-config-ty08ptc2.js → task-config-gv6s04fp.js} +2 -2
  116. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  117. package/dist/tools/ctx-memory/types.d.ts +2 -0
  118. package/dist/tools/ctx-memory/types.d.ts.map +1 -1
  119. package/dist/tools/ctx-reduce/tools.d.ts.map +1 -1
  120. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  121. package/dist/transformers-node-wasm.js +25676 -0
  122. package/dist/transformers-web.js +24570 -0
  123. package/dist/transformers.node-b1wa0hgx.js +28808 -0
  124. package/package.json +8 -3
  125. package/src/config/index.test.ts +50 -4
  126. package/src/config/latch-permanence-guard.test.ts +5 -0
  127. package/src/config/schema/magic-context.test.ts +20 -0
  128. package/src/config/schema/magic-context.ts +8 -0
  129. package/src/shared/conflict-detector.test.ts +6 -19
  130. package/src/shared/conflict-fixer.test.ts +5 -8
  131. package/src/shared/index.ts +1 -0
  132. package/src/shared/logger.test.ts +135 -40
  133. package/src/shared/logger.ts +115 -2
  134. package/src/shared/model-resolution.test.ts +21 -19
  135. package/src/shared/model-resolution.ts +0 -1
  136. package/src/shared/rpc-utils.test.ts +246 -5
  137. package/src/shared/rpc-utils.ts +340 -49
  138. package/src/shared/summarize-child-stderr.test.ts +55 -0
  139. package/src/shared/summarize-child-stderr.ts +33 -0
  140. package/src/shared/test-temp-dir.test.ts +57 -0
  141. package/src/shared/test-temp-dir.ts +165 -0
  142. package/src/shared/window-geometry.ts +9 -0
  143. /package/dist/{index-rpgg35ms.js → index-8sseka65.js} +0 -0
package/dist/index.js CHANGED
@@ -1,3 +1,12 @@
1
+ import {
2
+ ANNOUNCEMENT_FEATURES,
3
+ ANNOUNCEMENT_FOOTER,
4
+ ANNOUNCEMENT_VERSION,
5
+ compareSemverCore,
6
+ isValidSemver,
7
+ markAnnouncementSeen,
8
+ shouldShowAnnouncement
9
+ } from "./index-dmvy29z0.js";
1
10
  import {
2
11
  CANONICAL_DREAM_TASKS,
3
12
  buildDreamTaskRuntimeConfigs,
@@ -8,12 +17,12 @@ import {
8
17
  leaseKindFor,
9
18
  processedDreamTaskItems,
10
19
  userMemoryCollectionEnabled
11
- } from "./index-knna83k0.js";
20
+ } from "./index-9j3tx8hy.js";
12
21
  import {
13
22
  resolveHistorianAgentOverrides,
14
23
  resolveHistorianModel,
15
24
  resolveOpenCodeAgentOverrides
16
- } from "./index-bf799gbq.js";
25
+ } from "./index-d1mxrd3p.js";
17
26
  import {
18
27
  AGE_RECLAIM_MIN_TOKENS,
19
28
  AdmissionClass,
@@ -46,6 +55,7 @@ import {
46
55
  MAX_EXECUTE_THRESHOLD,
47
56
  MagicContextConfigSchema,
48
57
  ModuleMemoryAuthorityError,
58
+ NEWEST_REASONING_BEARING_ASSISTANT,
49
59
  PER_HARNESS_MIGRATION_INVENTORY,
50
60
  PRIMER_CANDIDATE_MAX_AGE_MS,
51
61
  PRIMER_CANDIDATE_TTL_MS,
@@ -58,6 +68,7 @@ import {
58
68
  StaleRouteHandleError,
59
69
  SubcClient,
60
70
  SynapseEmbeddingProvider,
71
+ THINKING_BINDING_RECOVERY_FROZEN_PREFIX,
61
72
  ToolMutationBatch,
62
73
  USER_MEMORY_CANDIDATE_TTL_MS,
63
74
  V2_MEMORY_CATEGORIES,
@@ -76,6 +87,7 @@ import {
76
87
  applyMirroredNoteCompileFields,
77
88
  applyStrippedPlaceholderDelta,
78
89
  archiveMemory,
90
+ armThinkingBindingRecovery,
79
91
  assertTailHygieneContentUnchanged,
80
92
  backfillTagTokenCounts,
81
93
  beginBootQuietPeriod,
@@ -114,6 +126,7 @@ import {
114
126
  clearPersistedReasoningWatermark,
115
127
  clearPersistedTodoSyntheticAnchor,
116
128
  clearRecompStaging,
129
+ clearThinkingBindingRecoveryIf,
117
130
  computeCueContentHash,
118
131
  computeNormalizedHash,
119
132
  computeSyntheticCallId,
@@ -139,6 +152,8 @@ import {
139
152
  demoteTrailingBlankKeepDecisions,
140
153
  deriveTagLoadFloor,
141
154
  detectOverflow,
155
+ detectThinkingBindingMismatch,
156
+ directoryHasGitMetadata,
142
157
  dismissNote,
143
158
  dismissUserMemory,
144
159
  drainAuthority,
@@ -177,6 +192,7 @@ import {
177
192
  getActiveTagsBySession,
178
193
  getActiveToolTagsForAgeReclaim,
179
194
  getActiveUserMemories,
195
+ getAssignableTagNumberByMessageId,
180
196
  getAuthorityManagedMarker,
181
197
  getAutoSearchHintDecisions,
182
198
  getCandidateToolOwners,
@@ -196,6 +212,7 @@ import {
196
212
  getEmergencyRecoveryArmedAt,
197
213
  getHistorianFailureState,
198
214
  getIndexedMessageCorpusSize,
215
+ getInertWhitespaceAssistantTags,
199
216
  getLastCompartmentEndMessage,
200
217
  getLastCompartmentEndMessageId,
201
218
  getLastHookInitFailure,
@@ -232,6 +249,7 @@ import {
232
249
  getOverflowState,
233
250
  getPendingCompactionMarkerState,
234
251
  getPendingOps,
252
+ getPendingOpsCount,
235
253
  getPendingSmartNotes,
236
254
  getPersistedCompactionMarkerState,
237
255
  getPersistedNoteNudge,
@@ -258,10 +276,12 @@ import {
258
276
  getSourceContents,
259
277
  getStaleReduceStrippedIds,
260
278
  getStrippedPlaceholderIds,
279
+ getTagById,
261
280
  getTagNumberByMessageId,
262
281
  getTagsByNumbers,
263
282
  getTagsBySession,
264
283
  getTailHygieneTags,
284
+ getThinkingBindingRecoveryTarget,
265
285
  getToolTagNumberByOwner,
266
286
  getTrailingBlankDecisions,
267
287
  getTriggerTagTokenUpperBound,
@@ -294,6 +314,7 @@ import {
294
314
  isEditTool,
295
315
  isEmbeddingEnabled,
296
316
  isEmergencyRecoveryArmed,
317
+ isFable51ThinkingBindingModel,
297
318
  isFailClosedBlockingError,
298
319
  isFilePart,
299
320
  isLinkedGitWorktree,
@@ -320,6 +341,7 @@ import {
320
341
  markNoteNudgeDelivered,
321
342
  markNoteReady,
322
343
  markProjectLoadUntrusted,
344
+ markWhitespaceAssistantTagInert,
323
345
  memoryNeedsCue,
324
346
  mergeMemoryStats,
325
347
  migrateMagicContextConfigLocations,
@@ -475,6 +497,7 @@ import {
475
497
  tailHygieneStructuralSignature,
476
498
  takeDubiousOwnershipProjectIdentityWarning,
477
499
  textMentionsRecentCommit,
500
+ thinkingBindingRecoveryFrozenId,
478
501
  toModelEntry,
479
502
  toolOutputTokens,
480
503
  trimMemoriesToBudgetV2,
@@ -494,7 +517,7 @@ import {
494
517
  verificationFileExists,
495
518
  withContentLanguageDirective,
496
519
  withRawSessionMessageCache
497
- } from "./index-w8rkbhn6.js";
520
+ } from "./index-j8r0ejb3.js";
498
521
  import {
499
522
  deriveHistorianChunkTokens,
500
523
  deriveTriggerBudget,
@@ -504,12 +527,13 @@ import {
504
527
  isSaneLimit,
505
528
  refreshModelLimitsAfterAuthOnce,
506
529
  refreshModelLimitsFromApi,
530
+ reloadWindowOverlay,
507
531
  resolveHistorianContextLimit,
508
532
  setOutputReserveConfig,
509
533
  setStoragePrivatePermissionEnforcement,
510
534
  setWindowOverlayPath,
511
535
  shouldEnforcePrivateStoragePermissions
512
- } from "./index-2bykz3bx.js";
536
+ } from "./index-cvda5jb3.js";
513
537
  import {
514
538
  clearIgnoredMessages,
515
539
  describeError,
@@ -517,16 +541,17 @@ import {
517
541
  getErrorMessage,
518
542
  sendIgnoredMessage,
519
543
  sendUserPrompt
520
- } from "./index-88za3sz8.js";
544
+ } from "./index-zwjhba2r.js";
521
545
  import {
522
546
  advanceToolReclaimWatermark,
523
547
  clearSession,
524
548
  getOrCreateSessionMeta,
525
549
  logSlowWriteTransaction,
526
550
  markSessionCleanupPending,
551
+ retryPendingRustSessionCleanupsForProject,
527
552
  retryPendingSessionCleanups,
528
553
  updateSessionMeta
529
- } from "./index-x9dmzyp5.js";
554
+ } from "./index-y1yr57fw.js";
530
555
  import {
531
556
  modelRefLookupOrder,
532
557
  piModelRefToCanonical
@@ -539,28 +564,19 @@ import {
539
564
  isMidTurn,
540
565
  openCodeDbExists,
541
566
  withReadOnlySessionDb
542
- } from "./index-jmwq2wxr.js";
567
+ } from "./index-b9j85d29.js";
543
568
  import {
544
569
  cleanupConflictWarnings,
545
570
  detectConflicts,
546
571
  resolveCompactionForBoot,
547
572
  sendConflictWarning
548
- } from "./index-dmgbar7r.js";
573
+ } from "./index-eq9wr75c.js";
549
574
  import {
550
575
  detectConfigFile,
551
576
  isPrototypePollutionKey,
552
577
  parseJsonc,
553
578
  stripJsonComments
554
579
  } from "./index-8mtsfhyg.js";
555
- import {
556
- ANNOUNCEMENT_FEATURES,
557
- ANNOUNCEMENT_FOOTER,
558
- ANNOUNCEMENT_VERSION,
559
- compareSemverCore,
560
- isValidSemver,
561
- markAnnouncementSeen,
562
- shouldShowAnnouncement
563
- } from "./index-dmvy29z0.js";
564
580
  import {
565
581
  acknowledgeNotifications,
566
582
  drainNotifications,
@@ -568,13 +584,13 @@ import {
568
584
  pushNotification,
569
585
  registerNotificationSink
570
586
  } from "./index-d62ypazr.js";
571
- import"./index-26ds03jd.js";
587
+ import"./index-vnz6jvdc.js";
572
588
  import {
573
589
  getLoggerDiagnostics,
574
590
  hasShareabilitySensitiveText,
575
591
  log,
576
592
  sessionLog
577
- } from "./index-b4gv9f7g.js";
593
+ } from "./index-4x691bz5.js";
578
594
  import {
579
595
  getDataDir,
580
596
  getHarness,
@@ -11861,15 +11877,15 @@ Project memory uses exactly 5 categories. Every memory belongs to one:
11861
11877
  - **CONFIG_VALUES** — stable configuration keys/values and conventions. Not transient measurements (test counts, sizes, versions).
11862
11878
  - **NAMING** — naming conventions and canonical names. Not inventories.
11863
11879
 
11864
- **Legacy categories during transition:** older memories may still carry pre-v2 category names. When you touch one, map it to its 5-category home with \`action="update"\` (or \`merge\`): WORKFLOW_RULES→PROJECT_RULES, ARCHITECTURE_DECISIONS→ARCHITECTURE, CONFIG_DEFAULTS→CONFIG_VALUES, ENVIRONMENT→CONFIG_VALUES (paths) or CONSTRAINTS, KNOWN_ISSUES→CONSTRAINTS only if it's an external-system limit (otherwise archive — our own fixed bugs are not world facts). USER_DIRECTIVES / USER_PREFERENCES are NOT project categories — they live in the global user profile; archive project copies only when they add zero project-specific detail.`;
11880
+ **Legacy categories during transition:** older memories may still carry pre-v2 category names. When you touch one, map it to its 5-category home with \`action="update"\` (or \`merge\`): WORKFLOW_RULES→PROJECT_RULES, ARCHITECTURE_DECISIONS→ARCHITECTURE, CONFIG_DEFAULTS→CONFIG_VALUES, ENVIRONMENT→CONFIG_VALUES (paths) or CONSTRAINTS, KNOWN_ISSUES→CONSTRAINTS only if it's an external-system limit. USER_DIRECTIVES / USER_PREFERENCES are NOT project categories. Do not compare project memories with the global user profile or use it to justify an archive.`;
11865
11881
  var CURATE_SYSTEM_PROMPT = `You are a memory-pool curator for the magic-context system. You run during a scheduled dream window to keep a project's cross-session memory store lean and well-formed.
11866
11882
 
11867
11883
  ## Memory operations (ctx_memory)
11868
11884
  - \`action="list"\` — browse active memories, optionally filter by category
11869
11885
  - \`action="merge", ids=[N,M,...], content="...", category="..."\` — consolidate duplicates into one canonical memory
11870
- - \`action="update", ids=[N], content="..."\` — rewrite a memory's content
11886
+ - \`action="update", ids=[N], content="...", superseded_by=M\` — rewrite content; name where removed detail survives when cutting more than half
11871
11887
  - \`action="write", category="...", content="..."\` — create a memory (SPLITS ONLY — never mint new facts)
11872
- - \`action="archive", ids=[N], reason="..."\` — soft-archive a stale or low-value memory
11888
+ - \`action="archive", ids=[N], superseded_by=M, reason="..."\` — consolidate a redundant memory into the named active same-category survivor
11873
11889
 
11874
11890
  ## Rules
11875
11891
  1. **Assume the pool is accurate.** A separate verify task checks memories against code. You handle QUALITY only — duplicates, wording, low-value entries — never correctness, and you do NOT read the codebase.
@@ -11912,34 +11928,25 @@ Mapped files: ${files}${memory.hasNoFileSentinel ? " (file-independent)" : ""}`;
11912
11928
 
11913
11929
  `);
11914
11930
  }
11915
- function formatUserProfileList(userMemories) {
11916
- if (!userMemories || userMemories.length === 0)
11917
- return;
11918
- return userMemories.map((um) => `- [U${um.id}] ${um.content}`).join(`
11919
- `);
11920
- }
11921
11931
  function buildCuratePrompt(args) {
11922
11932
  return `## Task: Curate Project Memory Pool (hygiene)
11923
11933
 
11924
11934
  **Project:** ${args.projectPath}
11925
11935
 
11926
- The memories below are assumed ACCURATE (a separate verify task keeps them true). Your job is pool QUALITY: remove duplicates, tighten wording, and archive low-value entries that waste the ~6000-token injection budget. Explain each action in one line first. Do NOT mint new facts (that is the historian's job).
11936
+ The memories below are assumed ACCURATE (a separate verify task keeps them true). Your job is pool QUALITY: remove duplicates, tighten wording, and consolidate redundant entries that waste the ~6000-token injection budget. Explain each action in one line first. Do NOT mint new facts (that is the historian's job).
11927
11937
 
11928
11938
  Work ALL THREE phases below in order (A → B → C) over the whole pool. Do NOT stop after consolidating — a run that only merges and never improves or archives is incomplete.
11929
11939
 
11930
11940
  ### Phase A — Consolidate duplicates
11931
- Group by category, then merge near-identical / superset-subset / same-fact-different-angle clusters into one canonical memory with \`ctx_memory(action="merge", ids=[...], content="...", category="...")\`. Preserve every unique detail; terse present tense; paths/keys verbatim. Every id in a merge MUST share the same category — the system rejects cross-category merges. If two similar memories sit in different categories they are NOT duplicates (one is miscategorized — archive the redundant one in Phase C instead). One fact per memory.
11941
+ Group by category, then merge near-identical / superset-subset / same-fact-different-angle clusters into one canonical memory with \`ctx_memory(action="merge", ids=[...], content="...", category="...")\`. Preserve every unique detail; terse present tense; paths/keys verbatim. Every id in a merge MUST share the same category — the system rejects cross-category merges. If two similar memories sit in different categories they are NOT duplicates; do not archive either as a consolidation. One fact per memory.
11932
11942
 
11933
11943
  ### Phase B — Improve wording
11934
- Rewrite narrative/historical → operational present tense ("X uses Y because Z", not "we switched to Y"); drop session-local context and commit hashes (unless the hash is the point); add specifics where vague. \`write\` is for SPLITS ONLY (update the original down to its first fact, write the second) — a healthy run is net-neutral or net-shrinking, never net-adds facts.
11944
+ Rewrite narrative/historical → operational present tense ("X uses Y because Z", not "we switched to Y"); drop session-local context and commit hashes (unless the hash is the point); add specifics where vague. A rewrite that removes more than half the content must name the active same-category memory preserving that detail with \`superseded_by\`. \`write\` is for SPLITS ONLY (update the original down to its first fact, write the second) — a healthy run is net-neutral or net-shrinking, never net-adds facts.
11935
11945
 
11936
- ### Phase C — Archive stale / low-value
11937
- Archive (with a specific reason) memories that: restate code without rationale · are redundant with a better memory · are stale implementation detail (line numbers/internals) · low signal (seen_count=1, retrieval_count=0, no constraint language) · bare config value · transient measurement · a solved bug in OUR OWN code · redundant with the global user profile (zero added project detail).
11946
+ ### Phase C — Archive only into a surviving project memory
11947
+ Archive a redundant memory only when a better ACTIVE memory in the same project and category preserves its information; name that survivor with \`superseded_by\`. A bare "redundant" verdict is deletion and will be refused. Leave standalone low-value or stale entries unchanged for a human to review. The global user profile describes the operator and is never a substitute for project knowledge, so it cannot justify an archive.
11938
11948
  KEEP (overrides archive): constraint/rule language (must/never/always) · explains WHY (because/so that/to prevent) · EXTERNAL-system limit (CONSTRAINTS: archive only if word-for-word duplicated) · path/config WITH context · retrieval_count>0 · priority/philosophy.
11939
- ${args.userProfile ? `
11940
- ### Global user profile (for the redundancy check)
11941
- ${args.userProfile}
11942
- ` : ""}
11949
+
11943
11950
  ### Memory pool
11944
11951
  ${renderMemoryList(args.memories)}`;
11945
11952
  }
@@ -12148,8 +12155,7 @@ function buildDreamTaskPrompt(task, args) {
12148
12155
  case "curate":
12149
12156
  return buildCuratePrompt({
12150
12157
  projectPath: args.projectPath,
12151
- memories: args.curate?.memories ?? [],
12152
- userProfile: formatUserProfileList(args.userMemories)
12158
+ memories: args.curate?.memories ?? []
12153
12159
  });
12154
12160
  case "maintain-docs":
12155
12161
  return buildMaintainDocsPrompt(args.projectPath, args.lastDreamAt ?? null, args.existingDocs ?? { architecture: false, structure: false });
@@ -13401,6 +13407,7 @@ function showToast(ctx, title, message, variant = "info", duration = 3000) {
13401
13407
  function createLiveSessionState() {
13402
13408
  return {
13403
13409
  liveModelBySession: new Map,
13410
+ latestAssistantMessageIdBySession: new Map,
13404
13411
  variantBySession: new Map,
13405
13412
  agentBySession: new Map,
13406
13413
  channel1StateBySession: new Map,
@@ -16115,6 +16122,87 @@ function recordInvocation(args, startedAt, params) {
16115
16122
  });
16116
16123
  }
16117
16124
 
16125
+ // src/features/magic-context/dreamer/memory-claim-safety.ts
16126
+ var POLICY_SENTENCE_START = /(?:^|[.!?]\s+|\n\s*)(?:[-*]\s+|\d+[.)]\s+)?(?:you\s+|we\s+)?(?:must(?:\s+not)?|never|always|do\s+not|don't|shall\s+not)\b/i;
16127
+ var ACTOR_POLICY = /\b(?:you|we|agents?|masons?|workers?|operators?|users?|maintainers?)\s+(?:must(?:\s+not)?|should(?:\s+not)?|need\s+to|shall(?:\s+not)?|cannot|can't|may\s+not)\b/i;
16128
+ var BEHAVIORAL_WHEN_CLAUSE = /\bwhen\s+(?:you(?:'re|\s+are)?|we(?:'re|\s+are)?|told|asked|requested|working|debugging|reviewing|checking|verifying|investigating|using|running|editing|changing)\b/i;
16129
+ var WORKFLOW_IMPERATIVE = /(?:^|[.!?]\s+|\n\s*)(?:[-*]\s+|\d+[.)]\s+)?(?:please\s+)?(?:run|use|check|ask|avoid|prefer|ensure|keep|follow|brief|report|inspect|search|open|read|review|validate|confirm|delegate|stop|start|remember)\b/i;
16130
+ var DECISION_AUTHORITY = /\b(?:the\s+)?(?:user|operator|maintainer|owner)\s+(?:decides|chooses|approves|has\s+(?:the\s+)?final\s+say)\b/i;
16131
+ function isDirectiveShapedProjectRule(category, content) {
16132
+ if (category !== "PROJECT_RULES")
16133
+ return false;
16134
+ const text = content.trim();
16135
+ if (!text)
16136
+ return false;
16137
+ return POLICY_SENTENCE_START.test(text) || ACTOR_POLICY.test(text) || BEHAVIORAL_WHEN_CLAUSE.test(text) || WORKFLOW_IMPERATIVE.test(text) || DECISION_AUTHORITY.test(text);
16138
+ }
16139
+
16140
+ // src/features/magic-context/dreamer/curate-memory-safety.ts
16141
+ var PROJECT_SCOPED_CATEGORIES = new Set([
16142
+ "PROJECT_RULES",
16143
+ "ARCHITECTURE",
16144
+ "CONSTRAINTS",
16145
+ "CONFIG_VALUES",
16146
+ "NAMING"
16147
+ ]);
16148
+ var USER_PROFILE_REFERENCE = /\buser(?:[\s_-]+)(?:profile|preferences?)\b|\bU\d+\b/i;
16149
+ var refusalCountsBySession = new Map;
16150
+ function isSurvivingConsolidationTarget(memory, successor, projectIdentity) {
16151
+ return successor !== null && successor.id !== memory.id && successor.status === "active" && successor.supersededByMemoryId === null && (successor.expiresAt === null || successor.expiresAt > Date.now()) && projectIdentity(successor) === projectIdentity(memory) && successor.category === memory.category;
16152
+ }
16153
+ function assessCurateMutationSafety(args) {
16154
+ const { memory, successor, verdict } = args;
16155
+ if (verdict === "archive") {
16156
+ if (!isSurvivingConsolidationTarget(memory, successor, args.projectIdentity)) {
16157
+ return {
16158
+ memoryId: memory.id,
16159
+ verdict,
16160
+ reason: "missing-active-same-category-successor"
16161
+ };
16162
+ }
16163
+ if (PROJECT_SCOPED_CATEGORIES.has(memory.category) && USER_PROFILE_REFERENCE.test(args.reason ?? "")) {
16164
+ return {
16165
+ memoryId: memory.id,
16166
+ verdict,
16167
+ reason: "user-profile-is-not-project-memory"
16168
+ };
16169
+ }
16170
+ }
16171
+ if (isDirectiveShapedProjectRule(memory.category, memory.content)) {
16172
+ return {
16173
+ memoryId: memory.id,
16174
+ verdict,
16175
+ reason: "directive-shaped-project-rule"
16176
+ };
16177
+ }
16178
+ if (verdict === "update") {
16179
+ const originalChars = memory.content.trim().length;
16180
+ const replacementChars = args.replacementContent?.trim().length ?? 0;
16181
+ if (replacementChars * 2 < originalChars && !isSurvivingConsolidationTarget(memory, successor, args.projectIdentity)) {
16182
+ return {
16183
+ memoryId: memory.id,
16184
+ verdict,
16185
+ reason: "content-loss",
16186
+ originalChars,
16187
+ replacementChars
16188
+ };
16189
+ }
16190
+ }
16191
+ return null;
16192
+ }
16193
+ function recordCurateSafetyRefusal(sessionId, refusal) {
16194
+ const count = (refusalCountsBySession.get(sessionId) ?? 0) + 1;
16195
+ refusalCountsBySession.set(sessionId, count);
16196
+ const lengths = refusal.originalChars === undefined ? "" : ` original_chars=${refusal.originalChars} replacement_chars=${refusal.replacementChars ?? 0}`;
16197
+ log(`[dreamer] curate safety refusal: session_id=${sessionId} memory_id=${refusal.memoryId} verdict=${refusal.verdict} reason=${refusal.reason}${lengths} refused=${count}`);
16198
+ return count;
16199
+ }
16200
+ function takeCurateSafetyRefusalCount(sessionId) {
16201
+ const count = refusalCountsBySession.get(sessionId) ?? 0;
16202
+ refusalCountsBySession.delete(sessionId);
16203
+ return count;
16204
+ }
16205
+
16118
16206
  // src/agents/smart-note-compiler.ts
16119
16207
  var SMART_NOTE_COMPILER_AGENT = "smart-note-compiler";
16120
16208
 
@@ -16680,7 +16768,7 @@ function getAsyncModule() {
16680
16768
  asyncModulePromise ??= (async () => {
16681
16769
  const [{ default: singlefileAsyncifyVariant }, { newQuickJSAsyncWASMModuleFromVariant }] = await Promise.all([
16682
16770
  import("./index-rgdqj8nj.js").then((m)=>__toESM(m.default,1)),
16683
- import("./index-rpgg35ms.js")
16771
+ import("./index-8sseka65.js")
16684
16772
  ]);
16685
16773
  return newQuickJSAsyncWASMModuleFromVariant(singlefileAsyncifyVariant);
16686
16774
  })();
@@ -18138,21 +18226,6 @@ function validateMapMemoriesManifest(text, expectedIds) {
18138
18226
  return parsed;
18139
18227
  }
18140
18228
 
18141
- // src/features/magic-context/dreamer/memory-claim-safety.ts
18142
- var POLICY_SENTENCE_START = /(?:^|[.!?]\s+|\n\s*)(?:[-*]\s+|\d+[.)]\s+)?(?:you\s+|we\s+)?(?:must(?:\s+not)?|never|always|do\s+not|don't|shall\s+not)\b/i;
18143
- var ACTOR_POLICY = /\b(?:you|we|agents?|masons?|workers?|operators?|users?|maintainers?)\s+(?:must(?:\s+not)?|should(?:\s+not)?|need\s+to|shall(?:\s+not)?|cannot|can't|may\s+not)\b/i;
18144
- var BEHAVIORAL_WHEN_CLAUSE = /\bwhen\s+(?:you(?:'re|\s+are)?|we(?:'re|\s+are)?|told|asked|requested|working|debugging|reviewing|checking|verifying|investigating|using|running|editing|changing)\b/i;
18145
- var WORKFLOW_IMPERATIVE = /(?:^|[.!?]\s+|\n\s*)(?:[-*]\s+|\d+[.)]\s+)?(?:please\s+)?(?:run|use|check|ask|avoid|prefer|ensure|keep|follow|brief|report|inspect|search|open|read|review|validate|confirm|delegate|stop|start|remember)\b/i;
18146
- var DECISION_AUTHORITY = /\b(?:the\s+)?(?:user|operator|maintainer|owner)\s+(?:decides|chooses|approves|has\s+(?:the\s+)?final\s+say)\b/i;
18147
- function isDirectiveShapedProjectRule(category, content) {
18148
- if (category !== "PROJECT_RULES")
18149
- return false;
18150
- const text = content.trim();
18151
- if (!text)
18152
- return false;
18153
- return POLICY_SENTENCE_START.test(text) || ACTOR_POLICY.test(text) || BEHAVIORAL_WHEN_CLAUSE.test(text) || WORKFLOW_IMPERATIVE.test(text) || DECISION_AUTHORITY.test(text);
18154
- }
18155
-
18156
18229
  // src/features/magic-context/dreamer/map-memories.ts
18157
18230
  var MAP_BATCH_SIZE = 80;
18158
18231
  var MAP_BATCH_FLOOR_MS = 240000;
@@ -20423,7 +20496,8 @@ function createDreamTaskExecutor(deps) {
20423
20496
  deadline,
20424
20497
  parent,
20425
20498
  recordRun,
20426
- computeMemoryDelta
20499
+ computeMemoryDelta,
20500
+ reportProgress
20427
20501
  });
20428
20502
  } catch (error) {
20429
20503
  const { transient, brief } = classifyFailure(error);
@@ -20708,7 +20782,6 @@ async function runAgenticTask(config, ctx, helpers) {
20708
20782
  architecture: existsSync10(`${docsDir}/ARCHITECTURE.md`),
20709
20783
  structure: existsSync10(`${docsDir}/STRUCTURE.md`)
20710
20784
  } : undefined;
20711
- const userMemories = task === "curate" ? getActiveUserMemories(db).map((um) => ({ id: um.id, content: um.content })) : undefined;
20712
20785
  let curateMemories;
20713
20786
  if (task === "curate") {
20714
20787
  curateMemories = loadActiveMemoryPromptMemories(db, projectIdentity);
@@ -20718,7 +20791,6 @@ async function runAgenticTask(config, ctx, helpers) {
20718
20791
  projectPath: projectIdentity,
20719
20792
  lastDreamAt: lastRunAt ? String(lastRunAt) : null,
20720
20793
  existingDocs,
20721
- userMemories,
20722
20794
  curate: curateMemories ? { memories: curateMemories } : undefined
20723
20795
  });
20724
20796
  const abortController = new AbortController;
@@ -20776,6 +20848,11 @@ async function runAgenticTask(config, ctx, helpers) {
20776
20848
  });
20777
20849
  if (leaseLost)
20778
20850
  throw new Error("Dream lease lost during task");
20851
+ const curateRefused = task === "curate" ? takeCurateSafetyRefusalCount(sessionId) : 0;
20852
+ if (curateRefused > 0) {
20853
+ helpers.reportProgress(curateRefused, curateRefused);
20854
+ log(`[dreamer] curate safety summary: refused=${curateRefused}`);
20855
+ }
20779
20856
  if (parent) {
20780
20857
  recordChildInvocation({
20781
20858
  db,
@@ -20796,11 +20873,14 @@ async function runAgenticTask(config, ctx, helpers) {
20796
20873
  }
20797
20874
  }
20798
20875
  helpers.recordRun("completed", null, {
20799
- memoryChanges: helpers.computeMemoryDelta(memoryBefore)
20876
+ memoryChanges: helpers.computeMemoryDelta(memoryBefore),
20877
+ progress: curateRefused > 0 ? `curate: refused ${curateRefused} unsafe mutation(s)` : null
20800
20878
  });
20801
20879
  return { status: "completed" };
20802
20880
  } finally {
20803
20881
  heartbeat.stop();
20882
+ if (childSessionId)
20883
+ takeCurateSafetyRefusalCount(childSessionId);
20804
20884
  if (childSessionId) {
20805
20885
  await deps.client.session.delete({ path: { id: childSessionId } }).catch(() => {});
20806
20886
  }
@@ -21520,7 +21600,7 @@ function runTick(origin) {
21520
21600
  const db = openTimerDatabaseOrNull("maintenance tick");
21521
21601
  if (!db)
21522
21602
  return;
21523
- runMessageHistoryMaintenance(db);
21603
+ await runMessageHistoryMaintenance(db);
21524
21604
  for (const reg of registeredProjects.values()) {
21525
21605
  if (origin === "startup") {
21526
21606
  scheduleInitialProjectRun(reg, db);
@@ -21536,11 +21616,26 @@ function runTick(origin) {
21536
21616
  }
21537
21617
  })();
21538
21618
  }
21539
- function runMessageHistoryMaintenance(db) {
21619
+ async function runMessageHistoryMaintenance(db) {
21540
21620
  const cleanup = retryPendingSessionCleanups(db);
21541
21621
  if (cleanup.cleared > 0 || cleanup.failedSessionIds.length > 0) {
21542
21622
  log(`[message-index] pending session cleanup: cleared=${cleanup.cleared} failed=${cleanup.failedSessionIds.length}`);
21543
21623
  }
21624
+ for (const registration of registeredProjects.values()) {
21625
+ const moduleClient = registration.sessionCleanupModuleClient;
21626
+ if (!moduleClient)
21627
+ continue;
21628
+ const rustCleanup = await retryPendingRustSessionCleanupsForProject(db, registration.projectIdentity, async (sessionId) => {
21629
+ try {
21630
+ await moduleClient.deleteSession(sessionId, registration.directory);
21631
+ } finally {
21632
+ moduleClient.closeSession?.(sessionId);
21633
+ }
21634
+ });
21635
+ if (rustCleanup.cleared > 0 || rustCleanup.failedSessionIds.length > 0) {
21636
+ log(`[message-index] pending Rust session cleanup: cleared=${rustCleanup.cleared} failed=${rustCleanup.failedSessionIds.length}`);
21637
+ }
21638
+ }
21544
21639
  const sweep = sweepOrphanedOpenCodeMessageIndexes(db, openOpenCodeDb);
21545
21640
  if (sweep.deleted > 0) {
21546
21641
  log(`[message-index] orphan sweep: scanned=${sweep.scanned} deleted=${sweep.deleted} cursor=${sweep.cursor || "<complete>"}`);
@@ -21940,6 +22035,7 @@ function fallbackConfig(config, provider) {
21940
22035
  return {
21941
22036
  provider: "local",
21942
22037
  model: model || DEFAULT_LOCAL_EMBEDDING_MODEL,
22038
+ local_runtime: raw.local_runtime === "native" || raw.local_runtime === "wasm" ? raw.local_runtime : "auto",
21943
22039
  ...maxInputTokens !== undefined ? { max_input_tokens: maxInputTokens } : {}
21944
22040
  };
21945
22041
  }
@@ -22539,7 +22635,7 @@ function createTagger() {
22539
22635
  if (type === "tool") {
22540
22636
  throw new Error("tagger.assignTag: type='tool' is forbidden — use assignToolTag(sessionId, callId, ownerMsgId, ...)");
22541
22637
  }
22542
- return allocateTag(sessionId, messageId, type, byteSize2, db, reasoningByteSize, toolName, inputByteSize, null, messageId, () => getTagNumberByMessageId(db, sessionId, messageId), entryFingerprint, tokenThunk);
22638
+ return allocateTag(sessionId, messageId, type, byteSize2, db, reasoningByteSize, toolName, inputByteSize, null, messageId, () => getAssignableTagNumberByMessageId(db, sessionId, messageId), entryFingerprint, tokenThunk);
22543
22639
  }
22544
22640
  function backfillToolTokensIfNull(db, sessionId, tagNumber, tokenThunk) {
22545
22641
  if (!tokenThunk)
@@ -23253,8 +23349,16 @@ var GIT_COMMIT_SOURCE_BOOST = 1.2;
23253
23349
  var PRIMER_SOURCE_BOOST = 1.25;
23254
23350
  var messageSearchStatements = new WeakMap;
23255
23351
  var messageSearchStatementsWithCutoff = new WeakMap;
23352
+ var messageSearchDiagnosticStatements = new WeakMap;
23256
23353
  var batchedMessageSearchStatements = new WeakMap;
23257
23354
  var batchedFtsCountStatements = new WeakMap;
23355
+ function createUnifiedSearchDiagnostics() {
23356
+ return {
23357
+ suppressedVisibleMemoryIds: [],
23358
+ suppressedLiveMessageMatches: 0,
23359
+ gitCommitUnavailable: null
23360
+ };
23361
+ }
23258
23362
  function normalizeLimit(limit) {
23259
23363
  if (typeof limit !== "number" || !Number.isFinite(limit)) {
23260
23364
  return DEFAULT_UNIFIED_SEARCH_LIMIT;
@@ -23345,6 +23449,51 @@ function getMessageSearchStatementWithCutoff(db) {
23345
23449
  }
23346
23450
  return stmt;
23347
23451
  }
23452
+ function getMessageSearchDiagnosticStatement(db) {
23453
+ let stmt = messageSearchDiagnosticStatements.get(db);
23454
+ if (!stmt) {
23455
+ stmt = db.prepare(`
23456
+ WITH matches AS MATERIALIZED (
23457
+ SELECT
23458
+ message_ordinal AS messageOrdinal,
23459
+ message_id AS messageId,
23460
+ role,
23461
+ content,
23462
+ CAST(message_ordinal AS INTEGER) AS ordinalValue,
23463
+ bm25(message_history_fts) AS ftsRank
23464
+ FROM message_history_fts
23465
+ WHERE session_id = ? AND message_history_fts MATCH ?
23466
+ ),
23467
+ eligible AS (
23468
+ SELECT * FROM matches
23469
+ WHERE ordinalValue <= ?
23470
+ ORDER BY ftsRank, ordinalValue ASC
23471
+ LIMIT ?
23472
+ ),
23473
+ summary AS (
23474
+ SELECT COUNT(*) AS suppressedCount
23475
+ FROM matches
23476
+ WHERE ordinalValue > ?
23477
+ )
23478
+ SELECT
23479
+ eligible.messageOrdinal,
23480
+ eligible.messageId,
23481
+ eligible.role,
23482
+ eligible.content,
23483
+ eligible.ftsRank,
23484
+ summary.suppressedCount,
23485
+ 0 AS summaryOnly
23486
+ FROM eligible CROSS JOIN summary
23487
+ UNION ALL
23488
+ SELECT NULL, NULL, NULL, NULL, NULL, summary.suppressedCount, 1
23489
+ FROM summary
23490
+ WHERE NOT EXISTS (SELECT 1 FROM eligible)
23491
+ ORDER BY summaryOnly ASC, ftsRank ASC, messageOrdinal ASC
23492
+ `);
23493
+ messageSearchDiagnosticStatements.set(db, stmt);
23494
+ }
23495
+ return stmt;
23496
+ }
23348
23497
  function getBatchedFtsCountStatement(db, queryCount, cutoff) {
23349
23498
  let statements = batchedFtsCountStatements.get(db);
23350
23499
  if (!statements) {
@@ -23485,10 +23634,8 @@ function mergeMemoryResults(args) {
23485
23634
  const memoryById = new Map(args.memories.map((memory) => [memory.id, memory]));
23486
23635
  const candidateIds = new Set([...args.semanticScores.keys(), ...args.ftsScores.keys()]);
23487
23636
  const results = [];
23637
+ const suppressedVisibleIds = [];
23488
23638
  for (const id of candidateIds) {
23489
- if (args.visibleMemoryIds?.has(id)) {
23490
- continue;
23491
- }
23492
23639
  const memory = memoryById.get(id);
23493
23640
  if (!memory) {
23494
23641
  continue;
@@ -23510,6 +23657,10 @@ function mergeMemoryResults(args) {
23510
23657
  if (score <= 0) {
23511
23658
  continue;
23512
23659
  }
23660
+ if (args.visibleMemoryIds?.has(id)) {
23661
+ suppressedVisibleIds.push(id);
23662
+ continue;
23663
+ }
23513
23664
  results.push({
23514
23665
  source: "memory",
23515
23666
  content: previewText(memory.content),
@@ -23520,20 +23671,23 @@ function mergeMemoryResults(args) {
23520
23671
  sourceName: args.sourceNameByMemoryId?.get(memory.id)
23521
23672
  });
23522
23673
  }
23523
- return results.sort((left, right) => {
23524
- if (right.score !== left.score) {
23525
- return right.score - left.score;
23526
- }
23527
- return left.memoryId - right.memoryId;
23528
- }).slice(0, args.limit);
23674
+ return {
23675
+ results: results.sort((left, right) => {
23676
+ if (right.score !== left.score) {
23677
+ return right.score - left.score;
23678
+ }
23679
+ return left.memoryId - right.memoryId;
23680
+ }).slice(0, args.limit),
23681
+ suppressedVisibleIds: suppressedVisibleIds.sort((left, right) => left - right)
23682
+ };
23529
23683
  }
23530
23684
  async function searchMemories(args) {
23531
23685
  if (!args.memoryEnabled) {
23532
- return [];
23686
+ return { results: [], suppressedVisibleIds: [] };
23533
23687
  }
23534
23688
  const memories = args.workspace?.isWorkspaced ? getMemoriesByProjects(args.db, args.workspace.expandedIdentities, ["active", "permanent"], Date.now(), args.workspace.ownIdentities, args.workspace.shareCategories) : getMemoriesByProject(args.db, args.projectPath);
23535
23689
  if (memories.length === 0) {
23536
- return [];
23690
+ return { results: [], suppressedVisibleIds: [] };
23537
23691
  }
23538
23692
  const ftsMatches = getFtsMatches({
23539
23693
  db: args.db,
@@ -23610,6 +23764,21 @@ function runMessageFtsQuery(db, sessionId, ftsQuery, fetchLimit, cutoff) {
23610
23764
  }
23611
23765
  return result;
23612
23766
  }
23767
+ function runMessageFtsQueryWithDiagnostics(args) {
23768
+ if (args.ftsQuery.length === 0)
23769
+ return { rows: [], suppressedCount: 0 };
23770
+ const rawRows = getMessageSearchDiagnosticStatement(args.db).all(args.sessionId, args.ftsQuery, args.cutoff, args.fetchLimit, args.cutoff).map((row) => row);
23771
+ const suppressedCount = rawRows[0]?.suppressedCount ?? 0;
23772
+ const rows = [];
23773
+ for (const row of rawRows) {
23774
+ if (row.summaryOnly === 1)
23775
+ continue;
23776
+ const normalized = normalizeMessageSearchRow(row, args.cutoff);
23777
+ if (normalized)
23778
+ rows.push(normalized);
23779
+ }
23780
+ return { rows, suppressedCount };
23781
+ }
23613
23782
  function getBatchedMessageSearchStatement(db, queryCount, cutoff) {
23614
23783
  let statements = batchedMessageSearchStatements.get(db);
23615
23784
  if (!statements) {
@@ -23676,7 +23845,20 @@ function searchMessages(args) {
23676
23845
  const baseQuery = sanitizeFtsQuery(args.query.trim());
23677
23846
  const probes = args.probes ?? [];
23678
23847
  if (probes.length === 0) {
23679
- const filtered = runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff).slice(0, args.limit);
23848
+ const outcome = args.diagnostics && cutoff !== null ? runMessageFtsQueryWithDiagnostics({
23849
+ db: args.db,
23850
+ sessionId: args.sessionId,
23851
+ ftsQuery: baseQuery,
23852
+ fetchLimit,
23853
+ cutoff
23854
+ }) : {
23855
+ rows: runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff),
23856
+ suppressedCount: 0
23857
+ };
23858
+ if (args.diagnostics) {
23859
+ args.diagnostics.suppressedLiveMessageMatches = outcome.suppressedCount;
23860
+ }
23861
+ const filtered = outcome.rows.slice(0, args.limit);
23680
23862
  return filtered.map((row, rank) => ({
23681
23863
  source: "message",
23682
23864
  content: previewText(row.content),
@@ -23689,8 +23871,19 @@ function searchMessages(args) {
23689
23871
  const sanitizedProbes = probes.map((probe) => ({ probe, query: sanitizeFtsQuery(probe) })).filter((entry) => entry.query.length > 0);
23690
23872
  const corpusSize = getIndexedMessageCorpusSize(args.db, args.sessionId, cutoff);
23691
23873
  const probeCounts = countSessionFtsMatchesBatch(args.db, args.sessionId, sanitizedProbes.map((entry) => entry.query), cutoff);
23874
+ const collectBaseDiagnostics = args.diagnostics !== undefined && cutoff !== null;
23875
+ const baseOutcome = collectBaseDiagnostics && baseQuery.length > 0 ? runMessageFtsQueryWithDiagnostics({
23876
+ db: args.db,
23877
+ sessionId: args.sessionId,
23878
+ ftsQuery: baseQuery,
23879
+ fetchLimit,
23880
+ cutoff
23881
+ }) : null;
23882
+ if (args.diagnostics) {
23883
+ args.diagnostics.suppressedLiveMessageMatches = baseOutcome?.suppressedCount ?? 0;
23884
+ }
23692
23885
  const searchQueries = [
23693
- ...baseQuery.length > 0 ? [baseQuery] : [],
23886
+ ...!collectBaseDiagnostics && baseQuery.length > 0 ? [baseQuery] : [],
23694
23887
  ...sanitizedProbes.map((entry) => entry.query)
23695
23888
  ];
23696
23889
  const rowsByQuery = runMessageFtsQueriesBatch(args.db, args.sessionId, searchQueries, fetchLimit, cutoff);
@@ -23698,10 +23891,11 @@ function searchMessages(args) {
23698
23891
  let queryIndex = 0;
23699
23892
  if (baseQuery.length > 0) {
23700
23893
  queryLists.push({
23701
- rows: rowsByQuery[queryIndex] ?? [],
23894
+ rows: baseOutcome?.rows ?? rowsByQuery[queryIndex] ?? [],
23702
23895
  weight: 1
23703
23896
  });
23704
- queryIndex += 1;
23897
+ if (!collectBaseDiagnostics)
23898
+ queryIndex += 1;
23705
23899
  }
23706
23900
  const probeWeights = new Map;
23707
23901
  sanitizedProbes.forEach((entry, probeIndex) => {
@@ -24088,6 +24282,9 @@ function memoriesToIdLookupResults(args) {
24088
24282
  }));
24089
24283
  }
24090
24284
  function resolveMemoriesByIdsForSearch(args) {
24285
+ if (args.diagnostics) {
24286
+ args.diagnostics.suppressedVisibleMemoryIds = [];
24287
+ }
24091
24288
  if (args.ids.length === 0) {
24092
24289
  return null;
24093
24290
  }
@@ -24098,16 +24295,22 @@ function resolveMemoriesByIdsForSearch(args) {
24098
24295
  }
24099
24296
  const memoriesById = new Map(fetched.map((memory) => [memory.id, memory]));
24100
24297
  const ordered = [];
24298
+ const suppressedVisibleIds = new Set;
24101
24299
  for (const id of args.ids) {
24102
24300
  const memory = memoriesById.get(id);
24103
24301
  if (!memory)
24104
24302
  continue;
24105
- if (args.visibleMemoryIds?.has(id))
24303
+ if (args.visibleMemoryIds?.has(id)) {
24304
+ suppressedVisibleIds.add(id);
24106
24305
  continue;
24306
+ }
24107
24307
  ordered.push(memory);
24108
24308
  if (ordered.length >= args.limit)
24109
24309
  break;
24110
24310
  }
24311
+ if (args.diagnostics) {
24312
+ args.diagnostics.suppressedVisibleMemoryIds = [...suppressedVisibleIds].sort((left, right) => left - right);
24313
+ }
24111
24314
  if (ordered.length === 0) {
24112
24315
  return null;
24113
24316
  }
@@ -24129,6 +24332,11 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
24129
24332
  }
24130
24333
  const limit = normalizeLimit(options.limit);
24131
24334
  const tierLimit = Math.max(limit * 3, DEFAULT_UNIFIED_SEARCH_LIMIT);
24335
+ if (options.diagnostics) {
24336
+ options.diagnostics.suppressedVisibleMemoryIds = [];
24337
+ options.diagnostics.suppressedLiveMessageMatches = 0;
24338
+ options.diagnostics.gitCommitUnavailable = null;
24339
+ }
24132
24340
  const embeddingEnabled = options.embeddingEnabled ?? true;
24133
24341
  const embedQuery = options.embedQuery ?? embedText;
24134
24342
  const isEmbeddingRuntimeEnabled = options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
@@ -24138,6 +24346,9 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
24138
24346
  const runMemory = activeSources.has("memory") && memoryFeatureEnabled;
24139
24347
  const runMessages = activeSources.has("message");
24140
24348
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
24349
+ if (options.diagnostics && activeSources.has("git_commit") && options.gitRepositoryAvailable === false) {
24350
+ options.diagnostics.gitCommitUnavailable = "no_git_repository";
24351
+ }
24141
24352
  const runPrimers = activeSources.has("primer") && memoryFeatureEnabled;
24142
24353
  const runNotes = activeSources.has("note");
24143
24354
  const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
@@ -24154,7 +24365,8 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
24154
24365
  query: trimmedQuery,
24155
24366
  limit: tierLimit,
24156
24367
  maxOrdinal: options.maxMessageOrdinal,
24157
- probes: messageProbes
24368
+ probes: messageProbes,
24369
+ diagnostics: options.diagnostics
24158
24370
  }) : [];
24159
24371
  const capturedQuery = await queryEmbeddingPromise;
24160
24372
  const embeddingSnapshot = getProjectEmbeddingSnapshot(projectPath);
@@ -24178,7 +24390,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
24178
24390
  compartments: compartmentResults,
24179
24391
  limit: tierLimit
24180
24392
  });
24181
- const [memoryResults, gitCommitResults, primerResults, noteResults] = await Promise.all([
24393
+ const [memoryOutcome, gitCommitResults, primerResults, noteResults] = await Promise.all([
24182
24394
  runMemory ? searchMemories({
24183
24395
  db,
24184
24396
  projectPath,
@@ -24189,7 +24401,10 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
24189
24401
  queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
24190
24402
  workspace,
24191
24403
  visibleMemoryIds: options.visibleMemoryIds
24192
- }) : Promise.resolve([]),
24404
+ }) : Promise.resolve({
24405
+ results: [],
24406
+ suppressedVisibleIds: []
24407
+ }),
24193
24408
  runGitCommits ? Promise.resolve(searchGitCommits({
24194
24409
  db,
24195
24410
  projectPath,
@@ -24215,8 +24430,11 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
24215
24430
  probes: messageProbes
24216
24431
  })) : Promise.resolve([])
24217
24432
  ]);
24433
+ if (options.diagnostics) {
24434
+ options.diagnostics.suppressedVisibleMemoryIds = memoryOutcome.suppressedVisibleIds;
24435
+ }
24218
24436
  const results = [
24219
- ...memoryResults,
24437
+ ...memoryOutcome.results,
24220
24438
  ...primerResults,
24221
24439
  ...messageLikeResults,
24222
24440
  ...gitCommitResults,
@@ -24254,6 +24472,123 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
24254
24472
  }
24255
24473
  return results;
24256
24474
  }
24475
+ var SEARCH_NOTE_EXPAND_HINT = "Use ctx_expand(start=N-10, end=N) around any note @msg anchor above to read the surrounding conversation context.";
24476
+ function formatSearchAge(timestampMs) {
24477
+ const ageMs = Date.now() - timestampMs;
24478
+ if (ageMs < 0)
24479
+ return "future";
24480
+ const days = Math.floor(ageMs / (24 * 60 * 60 * 1000));
24481
+ if (days <= 0)
24482
+ return "today";
24483
+ if (days === 1)
24484
+ return "1d ago";
24485
+ if (days < 30)
24486
+ return `${days}d ago`;
24487
+ const months = Math.floor(days / 30);
24488
+ if (months === 1)
24489
+ return "1mo ago";
24490
+ if (months < 12)
24491
+ return `${months}mo ago`;
24492
+ const years = Math.floor(days / 365);
24493
+ return years === 1 ? "1y ago" : `${years}y ago`;
24494
+ }
24495
+ function formatUnifiedSearchResult(result, index, currentSessionId) {
24496
+ if (result.source === "memory") {
24497
+ const source = result.sourceName ? ` source=${result.sourceName}` : "";
24498
+ return [
24499
+ `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category}${source} match=${result.matchType}`,
24500
+ result.content
24501
+ ].join(`
24502
+ `);
24503
+ }
24504
+ if (result.source === "git_commit") {
24505
+ return [
24506
+ `[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatSearchAge(result.committedAtMs)} match=${result.matchType}`,
24507
+ result.content
24508
+ ].join(`
24509
+ `);
24510
+ }
24511
+ if (result.source === "primer") {
24512
+ return [
24513
+ `[${index}] [primer] score=${result.score.toFixed(2)} id=${result.primerId} support=${result.support} match=${result.matchType}`,
24514
+ result.content
24515
+ ].join(`
24516
+ `);
24517
+ }
24518
+ if (result.source === "note") {
24519
+ const anchor = result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId ? ` @msg ${result.anchorOrdinal}` : "";
24520
+ return [
24521
+ `[${index}] [note] score=${result.score.toFixed(2)} id=#${result.noteId} status=${result.status} ${formatSearchAge(result.createdAt)}${anchor}`,
24522
+ result.content
24523
+ ].join(`
24524
+ `);
24525
+ }
24526
+ if (result.source === "compartment") {
24527
+ return [
24528
+ `[${index}] [message] score=${result.score.toFixed(2)} compartment_id=${result.compartmentId} range=${result.startOrdinal}-${result.endOrdinal} match=${result.matchType} title=${result.title}`,
24529
+ result.snippet ? `Snippet: ${result.snippet}` : result.content
24530
+ ].join(`
24531
+ `);
24532
+ }
24533
+ const expandStart = Math.max(1, result.messageOrdinal - 3);
24534
+ const expandEnd = result.messageOrdinal + 3;
24535
+ return [
24536
+ `[${index}] [message] score=${result.score.toFixed(2)} ordinal=${result.messageOrdinal} range=${expandStart}-${expandEnd} role=${result.role}`,
24537
+ result.content
24538
+ ].join(`
24539
+ `);
24540
+ }
24541
+ function formatSearchDiagnosticLines(results, diagnostics) {
24542
+ if (!diagnostics)
24543
+ return [];
24544
+ const lines = [];
24545
+ const visibleIds = [...diagnostics.suppressedVisibleMemoryIds].sort((left, right) => left - right);
24546
+ if (visibleIds.length > 0) {
24547
+ const count = visibleIds.length;
24548
+ const noun = count === 1 ? "match" : "matches";
24549
+ const ids = visibleIds.join(", ");
24550
+ if (results.some((result) => result.source === "memory")) {
24551
+ lines.push(`Memories: ${count} additional ${noun} suppressed because ${count === 1 ? "it is" : "they are"} already visible in your project-memory block (ids ${ids}).`);
24552
+ } else {
24553
+ lines.push(`Memories: ${count} ${noun} found, all already visible in your project-memory block (ids ${ids}).`);
24554
+ }
24555
+ }
24556
+ if (diagnostics.suppressedLiveMessageMatches > 0) {
24557
+ const count = diagnostics.suppressedLiveMessageMatches;
24558
+ lines.push(`Message history: ${count} raw-message ${count === 1 ? "match is" : "matches are"} newer than the last compartment boundary (already in your context).`);
24559
+ }
24560
+ if (diagnostics.gitCommitUnavailable === "no_git_repository") {
24561
+ lines.push("Git commits: no git repository — commit search unavailable for this project.");
24562
+ }
24563
+ return lines;
24564
+ }
24565
+ function formatSearchResults(query, results, currentSessionId, diagnostics) {
24566
+ const diagnosticLines = formatSearchDiagnosticLines(results, diagnostics);
24567
+ if (results.length === 0) {
24568
+ if (diagnosticLines.length > 0) {
24569
+ return `No hidden results found for "${query}".
24570
+
24571
+ ${diagnosticLines.join(`
24572
+ `)}`;
24573
+ }
24574
+ return `No results found for "${query}" across notes, memories, primers, git commits, or message history.`;
24575
+ }
24576
+ const bodyParts = results.map((result, index) => formatUnifiedSearchResult(result, index + 1, currentSessionId));
24577
+ if (diagnosticLines.length > 0)
24578
+ bodyParts.push(diagnosticLines.join(`
24579
+ `));
24580
+ if (results.some((result) => result.source === "message" || result.source === "compartment")) {
24581
+ bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
24582
+ }
24583
+ if (results.some((result) => result.source === "note" && result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId)) {
24584
+ bodyParts.push(SEARCH_NOTE_EXPAND_HINT);
24585
+ }
24586
+ return `Found ${results.length} result${results.length === 1 ? "" : "s"} for "${query}":
24587
+
24588
+ ${bodyParts.join(`
24589
+
24590
+ `)}`;
24591
+ }
24257
24592
 
24258
24593
  // src/hooks/magic-context/caveman.ts
24259
24594
  var PRESERVATION_PATTERNS = [
@@ -25180,16 +25515,14 @@ var WHOLE_MESSAGE_PLACEHOLDER_TEXT = "[dropped]";
25180
25515
  function modelAcceptsEmptyContent(providerID) {
25181
25516
  return providerID === "anthropic";
25182
25517
  }
25183
- function variantChangeBustsProviderCache(providerID) {
25184
- if (providerID === undefined)
25185
- return true;
25186
- if (providerID === "anthropic")
25187
- return true;
25188
- if (providerID === "google-vertex-anthropic")
25189
- return true;
25190
- if (providerID.includes("bedrock"))
25191
- return true;
25192
- return false;
25518
+ function variantChangeBustsProviderCache(providerID, modelID) {
25519
+ if (!providerID || !modelID)
25520
+ return false;
25521
+ const normalizedProviderID = providerID.toLowerCase();
25522
+ const isAnthropicFamily = normalizedProviderID === "anthropic" || normalizedProviderID === "google-vertex-anthropic" || normalizedProviderID.includes("bedrock");
25523
+ if (!isAnthropicFamily)
25524
+ return false;
25525
+ return !isFable51ThinkingBindingModel("anthropic", modelID);
25193
25526
  }
25194
25527
  function makeSentinel(originalPart) {
25195
25528
  const sentinel = {
@@ -25602,7 +25935,7 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
25602
25935
  const forceMaterializationPercentage = escalationBands(executeThresholdPercentage).forceMaterializationPercentage;
25603
25936
  if (usage.percentage >= forceMaterializationPercentage) {
25604
25937
  if (projectedPostDropPercentage !== null && projectedPostDropPercentage <= relativePostDropTarget) {
25605
- sessionLog(sessionId, `compartment trigger: skipping force band ${forceMaterializationPercentage}% because projected post-drop usage is ${projectedPostDropPercentage.toFixed(1)}% (target ${relativePostDropTarget.toFixed(1)}%)`);
25938
+ sessionLog(sessionId, `historian redundancy skip: summarizer not needed this pass — force band is ${forceMaterializationPercentage}%; queued/automatic drops are projected to reclaim to ${projectedPostDropPercentage.toFixed(1)}% (target ${relativePostDropTarget.toFixed(1)}%) on the next eligible execute pass`);
25606
25939
  return { shouldFire: false };
25607
25940
  }
25608
25941
  sessionLog(sessionId, `compartment trigger: force-firing at ${usage.percentage.toFixed(1)}% (projected post-drop ${formatProjectedPostDropPercentage(projectedPostDropPercentage)})`);
@@ -25663,7 +25996,7 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
25663
25996
  return { shouldFire: false };
25664
25997
  }
25665
25998
  if (projectedPostDropPercentage !== null && projectedPostDropPercentage <= relativePostDropTarget) {
25666
- sessionLog(sessionId, `compartment trigger: not firing at ${usage.percentage.toFixed(1)}% because projected post-drop usage is ${projectedPostDropPercentage.toFixed(1)}% (target ${relativePostDropTarget.toFixed(1)}%)`);
25999
+ sessionLog(sessionId, `historian redundancy skip: summarizer not needed this pass — usage is ${usage.percentage.toFixed(1)}%; queued/automatic drops are projected to reclaim to ${projectedPostDropPercentage.toFixed(1)}% (target ${relativePostDropTarget.toFixed(1)}%) on the next eligible execute pass`);
25667
26000
  return { shouldFire: false };
25668
26001
  }
25669
26002
  if (!tailInfo.hasProtectedEligibleHead || !tailInfo.isMeaningful) {
@@ -26541,20 +26874,23 @@ function createChatMessageHook(args) {
26541
26874
  args.agentBySession.set(sessionId, input.agent);
26542
26875
  }
26543
26876
  if (previousVariant !== undefined && input.variant !== undefined && previousVariant !== input.variant) {
26544
- const providerID = input.model?.providerID ?? args.liveModelBySession.get(sessionId)?.providerID;
26545
- if (variantChangeBustsProviderCache(providerID)) {
26877
+ const liveModel = args.liveModelBySession.get(sessionId);
26878
+ const providerID = input.model?.providerID ?? liveModel?.providerID;
26879
+ const modelID = input.model?.modelID ?? liveModel?.modelID;
26880
+ if (variantChangeBustsProviderCache(providerID, modelID)) {
26546
26881
  sessionLog(sessionId, `variant changed (${previousVariant} -> ${input.variant}), triggering flush`);
26547
26882
  args.historyRefreshSessions.add(sessionId);
26548
26883
  args.systemPromptRefreshSessions.add(sessionId);
26549
26884
  args.pendingMaterializationSessions.add(sessionId);
26550
26885
  args.lastHeuristicsTurnId.delete(sessionId);
26551
26886
  } else {
26552
- sessionLog(sessionId, `variant changed (${previousVariant} -> ${input.variant}) on provider ${providerID} whose cache ignores request params; deferring flush to next natural bust`);
26887
+ sessionLog(sessionId, `variant changed (${previousVariant} -> ${input.variant}) on ${providerID ?? "unknown"}/${modelID ?? "unknown"} without a proven natural cache bust; deferring flush to next natural bust`);
26553
26888
  }
26554
26889
  }
26555
26890
  };
26556
26891
  }
26557
26892
  function createEventHook(args) {
26893
+ const latestAssistantMessageIdBySession = args.latestAssistantMessageIdBySession ?? new Map;
26558
26894
  return async (input) => {
26559
26895
  await args.eventHandler(input);
26560
26896
  if (input.event.type === "message.updated") {
@@ -26568,24 +26904,31 @@ function createEventHook(args) {
26568
26904
  }
26569
26905
  const assistantInfo = getMessageUpdatedAssistantInfo(input.event.properties);
26570
26906
  if (assistantInfo?.providerID && assistantInfo?.modelID) {
26571
- const previous = args.liveModelBySession.get(assistantInfo.sessionID);
26572
- args.liveModelBySession.set(assistantInfo.sessionID, {
26573
- providerID: assistantInfo.providerID,
26574
- modelID: assistantInfo.modelID
26575
- });
26576
- if (previous && (previous.providerID !== assistantInfo.providerID || previous.modelID !== assistantInfo.modelID)) {
26577
- dropSlot(assistantInfo.sessionID, "model-change");
26578
- sessionLog(assistantInfo.sessionID, `model changed (${previous.providerID}/${previous.modelID} -> ${assistantInfo.providerID}/${assistantInfo.modelID}), clearing historian failure state and reasoning watermark`);
26579
- clearHistorianFailureState(args.db, assistantInfo.sessionID);
26580
- clearPersistedReasoningWatermark(args.db, assistantInfo.sessionID);
26581
- clearDetectedContextLimit(args.db, assistantInfo.sessionID);
26582
- clearEmergencyRecovery(args.db, assistantInfo.sessionID);
26583
- clearEmergencyDropSample(args.db, assistantInfo.sessionID);
26584
- updateSessionMeta(args.db, assistantInfo.sessionID, {
26585
- clearedReasoningThroughTag: 0,
26586
- observedSafeInputTokens: 0,
26587
- cacheAlertSent: false
26907
+ const latestMessageID = latestAssistantMessageIdBySession.get(assistantInfo.sessionID);
26908
+ const acceptsModelUpdate = latestMessageID === undefined || assistantInfo.messageID !== undefined && assistantInfo.messageID >= latestMessageID;
26909
+ if (acceptsModelUpdate) {
26910
+ if (assistantInfo.messageID !== undefined) {
26911
+ latestAssistantMessageIdBySession.set(assistantInfo.sessionID, assistantInfo.messageID);
26912
+ }
26913
+ const previous = args.liveModelBySession.get(assistantInfo.sessionID);
26914
+ args.liveModelBySession.set(assistantInfo.sessionID, {
26915
+ providerID: assistantInfo.providerID,
26916
+ modelID: assistantInfo.modelID
26588
26917
  });
26918
+ if (previous && (previous.providerID !== assistantInfo.providerID || previous.modelID !== assistantInfo.modelID)) {
26919
+ dropSlot(assistantInfo.sessionID, "model-change");
26920
+ sessionLog(assistantInfo.sessionID, `model changed (${previous.providerID}/${previous.modelID} -> ${assistantInfo.providerID}/${assistantInfo.modelID}), clearing historian failure state and reasoning watermark`);
26921
+ clearHistorianFailureState(args.db, assistantInfo.sessionID);
26922
+ clearPersistedReasoningWatermark(args.db, assistantInfo.sessionID);
26923
+ clearDetectedContextLimit(args.db, assistantInfo.sessionID);
26924
+ clearEmergencyRecovery(args.db, assistantInfo.sessionID);
26925
+ clearEmergencyDropSample(args.db, assistantInfo.sessionID);
26926
+ updateSessionMeta(args.db, assistantInfo.sessionID, {
26927
+ clearedReasoningThroughTag: 0,
26928
+ observedSafeInputTokens: 0,
26929
+ cacheAlertSent: false
26930
+ });
26931
+ }
26589
26932
  }
26590
26933
  }
26591
26934
  }
@@ -26598,6 +26941,7 @@ function createEventHook(args) {
26598
26941
  }
26599
26942
  if (input.event.type === "session.deleted") {
26600
26943
  args.liveModelBySession.delete(sessionId);
26944
+ latestAssistantMessageIdBySession.delete(sessionId);
26601
26945
  args.variantBySession.delete(sessionId);
26602
26946
  args.agentBySession.delete(sessionId);
26603
26947
  args.sessionDirectoryBySession.delete(sessionId);
@@ -27565,9 +27909,9 @@ function registerRpcHandlers(rpcServer, args) {
27565
27909
  return { count: buildCompartmentCount(db, sessionId, moduleStatus) };
27566
27910
  });
27567
27911
  const buildManagedCtx = async (db) => {
27568
- const { deriveHistorianChunkTokens: deriveHistorianChunkTokens2, resolveHistorianContextLimit: resolveHistorianContextLimit2 } = await import("./derive-budgets-3xfyphvk.js");
27569
- const { resolveHistorianModel: resolveHistorianModel2 } = await import("./model-resolution-jgrkt0z2.js");
27570
- const { userMemoryCollectionEnabled: userMemoryCollectionEnabled2 } = await import("./task-config-ty08ptc2.js");
27912
+ const { deriveHistorianChunkTokens: deriveHistorianChunkTokens2, resolveHistorianContextLimit: resolveHistorianContextLimit2 } = await import("./derive-budgets-9q2d3r93.js");
27913
+ const { resolveHistorianModel: resolveHistorianModel2 } = await import("./model-resolution-6w6wzw18.js");
27914
+ const { userMemoryCollectionEnabled: userMemoryCollectionEnabled2 } = await import("./task-config-gv6s04fp.js");
27571
27915
  const DEFAULT_HISTORIAN_TIMEOUT_MS2 = 600000;
27572
27916
  const historianModel = resolveHistorianModel2(config, "opencode");
27573
27917
  return {
@@ -27598,8 +27942,8 @@ function registerRpcHandlers(rpcServer, args) {
27598
27942
  const db = getDb();
27599
27943
  if (!db)
27600
27944
  return { ok: false, error: "db unavailable" };
27601
- const { runManagedRecomp: runManagedRecomp2 } = await import("./recomp-orchestrator-1w4xj6jt.js");
27602
- const { sendIgnoredMessage: sendIgnoredMessage2 } = await import("./send-session-notification-kn34g7mx.js");
27945
+ const { runManagedRecomp: runManagedRecomp2 } = await import("./recomp-orchestrator-m37p3fq6.js");
27946
+ const { sendIgnoredMessage: sendIgnoredMessage2 } = await import("./send-session-notification-bw7eex6w.js");
27603
27947
  log(`[rpc] recomp requested for session ${sessionId}`);
27604
27948
  const ctx = await buildManagedCtx(db);
27605
27949
  runManagedRecomp2(ctx, sessionId).then((message) => {
@@ -27617,8 +27961,8 @@ function registerRpcHandlers(rpcServer, args) {
27617
27961
  const db = getDb();
27618
27962
  if (!db)
27619
27963
  return { ok: false, error: "db unavailable" };
27620
- const { runManagedUpgrade: runManagedUpgrade2 } = await import("./recomp-orchestrator-1w4xj6jt.js");
27621
- const { sendIgnoredMessage: sendIgnoredMessage2 } = await import("./send-session-notification-kn34g7mx.js");
27964
+ const { runManagedUpgrade: runManagedUpgrade2 } = await import("./recomp-orchestrator-m37p3fq6.js");
27965
+ const { sendIgnoredMessage: sendIgnoredMessage2 } = await import("./send-session-notification-bw7eex6w.js");
27622
27966
  log(`[rpc] session-upgrade requested for session ${sessionId}`);
27623
27967
  const ctx = await buildManagedCtx(db);
27624
27968
  runManagedUpgrade2(ctx, sessionId).then((message) => {
@@ -27634,7 +27978,7 @@ function registerRpcHandlers(rpcServer, args) {
27634
27978
  if (!db)
27635
27979
  return { ok: false, error: "db unavailable" };
27636
27980
  try {
27637
- const { updateSessionMeta: updateSessionMeta2 } = await import("./storage-meta-session-snbzd26z.js");
27981
+ const { updateSessionMeta: updateSessionMeta2 } = await import("./storage-meta-session-dse3qv6s.js");
27638
27982
  updateSessionMeta2(db, sessionId, { upgradeRemindedAt: Date.now() });
27639
27983
  return { ok: true };
27640
27984
  } catch (error) {
@@ -28261,15 +28605,31 @@ function detectMidTurnBypassReason(input) {
28261
28605
  }
28262
28606
  function applyMidTurnDeferral(input) {
28263
28607
  if (input.base === "defer") {
28264
- return { midTurnAdjustedSchedulerDecision: "defer", sideEffect: "none" };
28608
+ return {
28609
+ midTurnAdjustedSchedulerDecision: "defer",
28610
+ sideEffect: "none",
28611
+ deferReason: "scheduler_defer"
28612
+ };
28265
28613
  }
28266
28614
  if (input.bypassReason !== "none") {
28267
- return { midTurnAdjustedSchedulerDecision: "execute", sideEffect: "none" };
28615
+ return {
28616
+ midTurnAdjustedSchedulerDecision: "execute",
28617
+ sideEffect: "none",
28618
+ deferReason: null
28619
+ };
28268
28620
  }
28269
28621
  if (input.midTurn) {
28270
- return { midTurnAdjustedSchedulerDecision: "defer", sideEffect: "set-flag" };
28622
+ return {
28623
+ midTurnAdjustedSchedulerDecision: "defer",
28624
+ sideEffect: "set-flag",
28625
+ deferReason: "mid_turn_boundary"
28626
+ };
28271
28627
  }
28272
- return { midTurnAdjustedSchedulerDecision: "execute", sideEffect: "none" };
28628
+ return {
28629
+ midTurnAdjustedSchedulerDecision: "execute",
28630
+ sideEffect: "none",
28631
+ deferReason: null
28632
+ };
28273
28633
  }
28274
28634
 
28275
28635
  // src/hooks/magic-context/cache-busting-signals.ts
@@ -30819,118 +31179,706 @@ class RawFallbackContextLimitError extends Error {
30819
31179
  }
30820
31180
  }
30821
31181
 
30822
- // src/hooks/magic-context/transform-context-state.ts
30823
- function loadPersistedUsageWatermark(db, sessionId) {
30824
- const result = db.prepare("SELECT last_response_time FROM session_meta WHERE session_id = ?").get(sessionId);
30825
- if (result === null || typeof result !== "object")
30826
- return null;
30827
- const lastResponseTime = result.last_response_time;
30828
- return typeof lastResponseTime === "number" ? lastResponseTime : null;
31182
+ // src/hooks/magic-context/strip-content.ts
31183
+ var DROPPED_PLACEHOLDER_PATTERN = /^\[dropped §\d+§\]$/;
31184
+ var TAG_PREFIX_PATTERN = /^§\d+§\s*/;
31185
+ var SYSTEM_INJECTION_PATTERNS = [
31186
+ /^<!-- OMO_INTERNAL_INITIATOR -->$/,
31187
+ /^<system-reminder>[\s\S]*<\/system-reminder>$/,
31188
+ /^\[SYSTEM DIRECTIVE:/,
31189
+ /^\[Category\+Skill Reminder\]/,
31190
+ /^\[EDIT ERROR - IMMEDIATE ACTION REQUIRED\]/,
31191
+ /^\[task CALL FAILED/,
31192
+ /^\[EMERGENCY CONTEXT WINDOW WARNING\]/
31193
+ ];
31194
+ function isSystemInjectedText(text) {
31195
+ const stripped = text.trim().replace(TAG_PREFIX_PATTERN, "").trim();
31196
+ if (stripped.length === 0)
31197
+ return false;
31198
+ return SYSTEM_INJECTION_PATTERNS.some((pattern) => pattern.test(stripped));
30829
31199
  }
30830
- function loadContextUsage(contextUsageMap, db, sessionId) {
30831
- const contextUsageEntry = contextUsageMap.get(sessionId);
30832
- try {
30833
- const persistedLastResponseTime = loadPersistedUsageWatermark(db, sessionId);
30834
- const cachedLastResponseTime = contextUsageEntry?.lastResponseTime ?? contextUsageEntry?.updatedAt;
30835
- if (contextUsageEntry && contextUsageEntry.lastResponseTime === undefined && (persistedLastResponseTime === null || persistedLastResponseTime === 0)) {
30836
- return contextUsageEntry.usage;
30837
- }
30838
- if (contextUsageEntry && cachedLastResponseTime === persistedLastResponseTime) {
30839
- return contextUsageEntry.usage;
31200
+ function stripSystemInjectedMessages(messages, protectedTailStart, providerID) {
31201
+ let stripped = 0;
31202
+ const sentineledIds = [];
31203
+ for (let i = 0;i < messages.length; i++) {
31204
+ if (i >= protectedTailStart)
31205
+ continue;
31206
+ const msg = messages[i];
31207
+ if (msg.parts.length === 0)
31208
+ continue;
31209
+ if (msg.info.role === "user")
31210
+ continue;
31211
+ if (msg.parts.length === 1 && isSentinel(msg.parts[0]))
31212
+ continue;
31213
+ let hasContentPart = false;
31214
+ let allContentIsSystemInjection = true;
31215
+ for (const part of msg.parts) {
31216
+ if (!isRecord(part))
31217
+ continue;
31218
+ const partType = part.type;
31219
+ if (METADATA_PART_TYPES.has(partType))
31220
+ continue;
31221
+ if (part.ignored === true)
31222
+ continue;
31223
+ if (partType === "tool") {
31224
+ allContentIsSystemInjection = false;
31225
+ break;
31226
+ }
31227
+ if (partType === "text" && typeof part.text === "string") {
31228
+ hasContentPart = true;
31229
+ if (!isSystemInjectedText(part.text)) {
31230
+ allContentIsSystemInjection = false;
31231
+ break;
31232
+ }
31233
+ continue;
31234
+ }
31235
+ allContentIsSystemInjection = false;
31236
+ break;
30840
31237
  }
30841
- const persisted = loadPersistedUsage(db, sessionId);
30842
- if (persisted) {
30843
- contextUsageMap.set(sessionId, {
30844
- ...persisted,
30845
- lastResponseTime: persistedLastResponseTime ?? persisted.updatedAt,
30846
- hasUsageTokens: false
30847
- });
30848
- return persisted.usage;
31238
+ if (hasContentPart && allContentIsSystemInjection) {
31239
+ msg.parts.length = 0;
31240
+ msg.parts.push(makeWholeMessageSentinel(providerID));
31241
+ stripped++;
31242
+ if (typeof msg.info.id === "string")
31243
+ sentineledIds.push(msg.info.id);
30849
31244
  }
30850
- contextUsageMap.delete(sessionId);
30851
- } catch (error) {
30852
- sessionLog(sessionId, "transform failed loading persisted usage:", error);
30853
- return contextUsageEntry?.usage ?? { percentage: 0, inputTokens: 0 };
30854
- }
30855
- return { percentage: 0, inputTokens: 0 };
30856
- }
30857
- function resolveSchedulerDecision(scheduler, sessionMeta, contextUsage, sessionId, modelKey, contextLimit) {
30858
- try {
30859
- const schedulerDecision = scheduler.shouldExecute(sessionMeta, contextUsage, undefined, sessionId, modelKey, contextLimit);
30860
- sessionLog(sessionId, `transform scheduler: percentage=${contextUsage.percentage.toFixed(1)}% inputTokens=${contextUsage.inputTokens} cacheTtl=${sessionMeta.cacheTtl} lastResponseTime=${sessionMeta.lastResponseTime} decision=${schedulerDecision}`);
30861
- return schedulerDecision;
30862
- } catch (error) {
30863
- sessionLog(sessionId, "transform scheduler failed; defaulting to defer:", error);
30864
- return "defer";
30865
- }
30866
- }
30867
-
30868
- // src/hooks/magic-context/channel2-cycle.ts
30869
- function rearmChannel2AfterCoverageAdvancingHardFold(input) {
30870
- if (input.compactionOff || !input.foldExecuted || input.previousCoverage === null || input.currentCoverage === null || input.currentCoverage <= input.previousCoverage) {
30871
- return false;
30872
31245
  }
30873
- return casChannel2NudgeState(input.db, input.sessionId, "delivered", "");
30874
- }
30875
- function rearmChannel2AfterMeasuredCollapse(input) {
30876
- if (!input.baseline.evaluable || input.baseline.generationInvalidated)
30877
- return false;
30878
- if (effectiveTailHygiene(input.baseline).u >= CHANNEL1_FLOOR_TOKENS)
30879
- return false;
30880
- return casChannel2NudgeState(input.db, input.sessionId, "delivered", "");
30881
- }
30882
-
30883
- // src/hooks/magic-context/drop-stale-reduce-calls.ts
30884
- var STALE_TOOL_NAMES = new Set(["ctx_reduce"]);
30885
- function isReduceToolPart(part) {
30886
- if (!isRecord(part))
30887
- return false;
30888
- if (part.type === "tool" && typeof part.tool === "string" && STALE_TOOL_NAMES.has(part.tool))
30889
- return true;
30890
- if (part.type === "tool-invocation" && typeof part.toolName === "string" && STALE_TOOL_NAMES.has(part.toolName))
30891
- return true;
30892
- if (part.type === "tool_use" && typeof part.name === "string" && STALE_TOOL_NAMES.has(part.name))
30893
- return true;
30894
- return false;
31246
+ return { stripped, sentineledIds };
30895
31247
  }
30896
- function hasAnyMeaningfulPart(parts) {
30897
- for (const part of parts) {
30898
- if (!isRecord(part))
31248
+ var METADATA_PART_TYPES = new Set([
31249
+ "step-start",
31250
+ "step-finish",
31251
+ "snapshot",
31252
+ "patch",
31253
+ "agent",
31254
+ "retry",
31255
+ "subtask",
31256
+ "compaction"
31257
+ ]);
31258
+ function stripDroppedPlaceholderMessages(messages, providerID) {
31259
+ let stripped = 0;
31260
+ const sentineledIds = [];
31261
+ for (let i = 0;i < messages.length; i++) {
31262
+ const msg = messages[i];
31263
+ if (msg.parts.length === 0)
30899
31264
  continue;
30900
- if (part.type === "text" && typeof part.text === "string" && part.text.trim().length > 0)
30901
- return true;
30902
- if (part.type === "thinking" || part.type === "reasoning" || part.type === "redacted_thinking")
31265
+ if (msg.info.role === "user")
30903
31266
  continue;
30904
- if (part.type === "meta" || part.type === "step-start" || part.type === "step-finish")
31267
+ if (msg.parts.length === 1 && isSentinel(msg.parts[0]))
30905
31268
  continue;
30906
- if (part.type !== "tool" || !isReduceToolPart(part))
30907
- return true;
31269
+ let hasContentPart = false;
31270
+ let hasNonDroppedContent = false;
31271
+ for (const part of msg.parts) {
31272
+ if (!isRecord(part))
31273
+ continue;
31274
+ const partType = part.type;
31275
+ if (METADATA_PART_TYPES.has(partType))
31276
+ continue;
31277
+ if (partType === "tool") {
31278
+ hasNonDroppedContent = true;
31279
+ break;
31280
+ }
31281
+ if (partType === "text" && typeof part.text === "string") {
31282
+ hasContentPart = true;
31283
+ const trimmed = part.text.trim();
31284
+ if (trimmed.length === 0)
31285
+ continue;
31286
+ if (!trimmed.includes("[dropped §")) {
31287
+ hasNonDroppedContent = true;
31288
+ break;
31289
+ }
31290
+ const allSegmentsDropped = trimmed.split(/(?=\[dropped §)/).filter((s) => s.trim().length > 0).every((segment) => DROPPED_PLACEHOLDER_PATTERN.test(segment.trim()));
31291
+ if (!allSegmentsDropped) {
31292
+ hasNonDroppedContent = true;
31293
+ break;
31294
+ }
31295
+ continue;
31296
+ }
31297
+ if (partType === "reasoning" && typeof part.text === "string") {
31298
+ hasContentPart = true;
31299
+ const trimmed = part.text.trim();
31300
+ if (trimmed.length === 0)
31301
+ continue;
31302
+ if (!trimmed.includes("[dropped §")) {
31303
+ hasNonDroppedContent = true;
31304
+ break;
31305
+ }
31306
+ const allSegmentsDropped = trimmed.split(/(?=\[dropped §)/).filter((s) => s.trim().length > 0).every((segment) => DROPPED_PLACEHOLDER_PATTERN.test(segment.trim()));
31307
+ if (!allSegmentsDropped) {
31308
+ hasNonDroppedContent = true;
31309
+ break;
31310
+ }
31311
+ continue;
31312
+ }
31313
+ hasNonDroppedContent = true;
31314
+ break;
31315
+ }
31316
+ if (hasContentPart && !hasNonDroppedContent) {
31317
+ msg.parts.length = 0;
31318
+ msg.parts.push(makeWholeMessageSentinel(providerID));
31319
+ stripped++;
31320
+ if (typeof msg.info.id === "string")
31321
+ sentineledIds.push(msg.info.id);
31322
+ }
30908
31323
  }
30909
- return false;
31324
+ return { stripped, sentineledIds };
30910
31325
  }
30911
- function messageHasReducePart(message) {
30912
- for (const part of message.parts) {
30913
- if (isSentinel(part))
31326
+ function replayClearedReasoning(messages, reasoningByMessage, messageTagNumbers, persistedWatermark) {
31327
+ if (persistedWatermark <= 0)
31328
+ return 0;
31329
+ let cleared = 0;
31330
+ for (const message of messages) {
31331
+ const msgTag = messageTagNumbers.get(message) ?? 0;
31332
+ if (msgTag === 0 || msgTag > persistedWatermark)
30914
31333
  continue;
30915
- if (isReduceToolPart(part))
30916
- return true;
31334
+ const parts = reasoningByMessage.get(message);
31335
+ if (!parts)
31336
+ continue;
31337
+ for (const tp of parts) {
31338
+ if (tp.thinking !== undefined && tp.thinking !== "[cleared]") {
31339
+ tp.thinking = "[cleared]";
31340
+ cleared++;
31341
+ }
31342
+ if (tp.text !== undefined && tp.text !== "[cleared]") {
31343
+ tp.text = "[cleared]";
31344
+ cleared++;
31345
+ }
31346
+ }
30917
31347
  }
30918
- return false;
31348
+ return cleared;
30919
31349
  }
30920
- function sentinelizeReduceParts(message) {
30921
- let touched = false;
30922
- for (let j = 0;j < message.parts.length; j++) {
30923
- const part = message.parts[j];
30924
- if (isSentinel(part))
31350
+ function replayStrippedInlineThinking(messages, messageTagNumbers, persistedWatermark) {
31351
+ if (persistedWatermark <= 0)
31352
+ return 0;
31353
+ let stripped = 0;
31354
+ for (const message of messages) {
31355
+ if (message.info.role !== "assistant")
30925
31356
  continue;
30926
- if (isReduceToolPart(part)) {
30927
- message.parts[j] = makeSentinel(part);
30928
- touched = true;
31357
+ const msgTag = messageTagNumbers.get(message) ?? 0;
31358
+ if (msgTag === 0 || msgTag > persistedWatermark)
31359
+ continue;
31360
+ for (const part of message.parts) {
31361
+ if (!isRecord(part) || part.type !== "text" || typeof part.text !== "string")
31362
+ continue;
31363
+ if (!part.text.includes("<think"))
31364
+ continue;
31365
+ const cleaned = part.text.replace(INLINE_THINKING_PATTERN, "");
31366
+ if (cleaned !== part.text) {
31367
+ part.text = cleaned;
31368
+ stripped++;
31369
+ }
30929
31370
  }
30930
31371
  }
30931
- if (touched && !hasAnyMeaningfulPart(message.parts)) {
30932
- message.parts.length = 0;
30933
- message.parts.push(makeSentinel(undefined));
31372
+ return stripped;
31373
+ }
31374
+ function clearOldReasoning(messages, reasoningByMessage, messageTagNumbers, clearReasoningAge) {
31375
+ const maxTag = findMaxTag(messageTagNumbers);
31376
+ if (maxTag === 0)
31377
+ return 0;
31378
+ const ageCutoff = maxTag - clearReasoningAge;
31379
+ let cleared = 0;
31380
+ for (const message of messages) {
31381
+ const msgTag = messageTagNumbers.get(message) ?? 0;
31382
+ if (msgTag === 0 || msgTag > ageCutoff)
31383
+ continue;
31384
+ const parts = reasoningByMessage.get(message);
31385
+ if (!parts)
31386
+ continue;
31387
+ for (const tp of parts) {
31388
+ if (tp.thinking !== undefined && tp.thinking !== "[cleared]") {
31389
+ tp.thinking = "[cleared]";
31390
+ cleared++;
31391
+ }
31392
+ if (tp.text !== undefined && tp.text !== "[cleared]") {
31393
+ tp.text = "[cleared]";
31394
+ cleared++;
31395
+ }
31396
+ }
31397
+ }
31398
+ return cleared;
31399
+ }
31400
+ function findMaxTag(messageTagNumbers) {
31401
+ let max = 0;
31402
+ for (const tag of messageTagNumbers.values()) {
31403
+ if (tag > max)
31404
+ max = tag;
31405
+ }
31406
+ return max;
31407
+ }
31408
+ var CLEARED_REASONING_TYPES = new Set(["thinking", "reasoning"]);
31409
+ function stripClearedReasoning(messages) {
31410
+ let stripped = 0;
31411
+ for (const message of messages) {
31412
+ if (message.info.role !== "assistant")
31413
+ continue;
31414
+ for (let i = 0;i < message.parts.length; i++) {
31415
+ const part = message.parts[i];
31416
+ if (!isRecord(part))
31417
+ continue;
31418
+ const partType = part.type;
31419
+ if (!CLEARED_REASONING_TYPES.has(partType))
31420
+ continue;
31421
+ if (!("thinking" in part) && !("text" in part))
31422
+ continue;
31423
+ const thinking = "thinking" in part ? part.thinking : undefined;
31424
+ const text = "text" in part ? part.text : undefined;
31425
+ const isCleared = (thinking === undefined || thinking === "[cleared]") && (text === undefined || text === "[cleared]");
31426
+ if (!isCleared)
31427
+ continue;
31428
+ message.parts[i] = makeSentinel(part);
31429
+ stripped++;
31430
+ }
31431
+ }
31432
+ return stripped;
31433
+ }
31434
+ var INLINE_THINKING_PATTERN = /<(?:thinking|think)>[\s\S]*?<\/(?:thinking|think)>\s*/g;
31435
+ function stripInlineThinking(messages, messageTagNumbers, clearReasoningAge) {
31436
+ const maxTag = findMaxTag(messageTagNumbers);
31437
+ if (maxTag === 0)
31438
+ return 0;
31439
+ const ageCutoff = maxTag - clearReasoningAge;
31440
+ let stripped = 0;
31441
+ for (const message of messages) {
31442
+ if (message.info.role !== "assistant")
31443
+ continue;
31444
+ const msgTag = messageTagNumbers.get(message) ?? 0;
31445
+ if (msgTag === 0 || msgTag > ageCutoff)
31446
+ continue;
31447
+ for (const part of message.parts) {
31448
+ if (!isRecord(part) || part.type !== "text" || typeof part.text !== "string")
31449
+ continue;
31450
+ const cleaned = part.text.replace(INLINE_THINKING_PATTERN, "");
31451
+ if (cleaned !== part.text) {
31452
+ part.text = cleaned;
31453
+ stripped++;
31454
+ }
31455
+ }
31456
+ }
31457
+ return stripped;
31458
+ }
31459
+ var REASONING_IGNORED_PART_TYPES = new Set([
31460
+ "step-start",
31461
+ "step-finish",
31462
+ "snapshot",
31463
+ "patch",
31464
+ "agent",
31465
+ "retry",
31466
+ "subtask",
31467
+ "compaction"
31468
+ ]);
31469
+ var REASONING_PART_TYPES = new Set(["reasoning", "thinking", "redacted_thinking"]);
31470
+ function hasReasoningReplayContent(message) {
31471
+ return message.parts.some((part) => {
31472
+ if (!isRecord(part) || part.ignored === true)
31473
+ return false;
31474
+ const type = typeof part.type === "string" ? part.type : "";
31475
+ if (REASONING_IGNORED_PART_TYPES.has(type))
31476
+ return false;
31477
+ return type !== "text" || typeof part.text !== "string" || part.text.trim().length > 0;
31478
+ });
31479
+ }
31480
+ function findNewestReasoningBearingAssistantId(messages) {
31481
+ for (let index = messages.length - 1;index >= 0; index -= 1) {
31482
+ const message = messages[index];
31483
+ if (message.info.role !== "assistant")
31484
+ continue;
31485
+ if (!message.parts.some((part) => isRecord(part) && REASONING_PART_TYPES.has(String(part.type)))) {
31486
+ continue;
31487
+ }
31488
+ const id = message.info.id;
31489
+ if (typeof id === "string" && id.length > 0)
31490
+ return id;
31491
+ }
31492
+ return;
31493
+ }
31494
+ function assistantHasReasoningPart(messages, messageId2) {
31495
+ return messages.some((message) => message.info.role === "assistant" && message.info.id === messageId2 && message.parts.some((part) => isRecord(part) && REASONING_PART_TYPES.has(String(part.type))));
31496
+ }
31497
+ function findLatestAssistantReasoningMutationExemptMessage(messages) {
31498
+ for (let index = messages.length - 1;index >= 0; index -= 1) {
31499
+ const message = messages[index];
31500
+ if (message.info.role === "assistant" && hasReasoningReplayContent(message))
31501
+ return message;
31502
+ }
31503
+ return;
31504
+ }
31505
+ function planMergedAssistantReasoningStrip(messages, mutationExemptMessage) {
31506
+ const plan = [];
31507
+ let prevRole;
31508
+ let keptReasoningInRun = false;
31509
+ for (const message of messages) {
31510
+ const role = message.info.role;
31511
+ if (role !== "assistant") {
31512
+ prevRole = role;
31513
+ keptReasoningInRun = false;
31514
+ continue;
31515
+ }
31516
+ const firstInRun = prevRole !== "assistant";
31517
+ if (firstInRun)
31518
+ keptReasoningInRun = false;
31519
+ if (message === mutationExemptMessage) {
31520
+ prevRole = role;
31521
+ continue;
31522
+ }
31523
+ let keepIndex = -1;
31524
+ if (firstInRun && !keptReasoningInRun) {
31525
+ for (let i = 0;i < message.parts.length; i++) {
31526
+ const part = message.parts[i];
31527
+ if (!isRecord(part))
31528
+ continue;
31529
+ const partType = part.type;
31530
+ if (REASONING_IGNORED_PART_TYPES.has(partType))
31531
+ continue;
31532
+ if (part.ignored === true)
31533
+ continue;
31534
+ if (isSentinel(part))
31535
+ continue;
31536
+ if (partType === "text" && typeof part.text === "string" && part.text.trim() === "") {
31537
+ continue;
31538
+ }
31539
+ if (REASONING_PART_TYPES.has(partType)) {
31540
+ keepIndex = i;
31541
+ }
31542
+ break;
31543
+ }
31544
+ }
31545
+ const stripIndices = [];
31546
+ for (let i = 0;i < message.parts.length; i++) {
31547
+ const part = message.parts[i];
31548
+ if (!isRecord(part))
31549
+ continue;
31550
+ if (!REASONING_PART_TYPES.has(part.type))
31551
+ continue;
31552
+ if (part.cache_control !== undefined)
31553
+ continue;
31554
+ if (i === keepIndex) {
31555
+ keptReasoningInRun = true;
31556
+ continue;
31557
+ }
31558
+ stripIndices.push(i);
31559
+ }
31560
+ if (stripIndices.length > 0)
31561
+ plan.push({ message, stripIndices });
31562
+ prevRole = role;
31563
+ }
31564
+ return plan;
31565
+ }
31566
+ var MAX_FROZEN_TRAILING_BLANKS = 1e4;
31567
+ var CANONICAL_BLANK_PART = { type: "text", text: "" };
31568
+ function isSentinelInvisibleTextPart(part) {
31569
+ return isRecord(part) && part.type === "text" && typeof part.text === "string" && part.text.trim() === "";
31570
+ }
31571
+ function isCanonicalBlankPart(part) {
31572
+ return isRecord(part) && Object.keys(part).length === 2 && part.type === "text" && part.text === "";
31573
+ }
31574
+ function trailingBlankKeepCount(decision) {
31575
+ if (decision === "keep")
31576
+ return 1;
31577
+ if (!decision.startsWith("keep:"))
31578
+ return;
31579
+ const countText = decision.slice("keep:".length);
31580
+ if (!/^[1-9]\d*$/.test(countText))
31581
+ return;
31582
+ const count = Number(countText);
31583
+ return Number.isSafeInteger(count) && count > 0 && count <= MAX_FROZEN_TRAILING_BLANKS ? count : undefined;
31584
+ }
31585
+ function trailingBlankDecisionForMessage(message) {
31586
+ const id = message.info.id;
31587
+ if (message.info.role !== "assistant" || typeof id !== "string" || id.length === 0) {
31588
+ return;
31589
+ }
31590
+ let trailingCount = 0;
31591
+ while (trailingCount < message.parts.length && isSentinelInvisibleTextPart(message.parts[message.parts.length - trailingCount - 1])) {
31592
+ trailingCount += 1;
31593
+ }
31594
+ if (trailingCount > MAX_FROZEN_TRAILING_BLANKS && trailingCount < message.parts.length) {
31595
+ return;
31596
+ }
31597
+ const decision = message.parts.length === 0 || trailingCount === message.parts.length ? "keep" : trailingCount === 0 ? "strip" : trailingCount === 1 ? "keep" : `keep:${trailingCount}`;
31598
+ return [id, decision];
31599
+ }
31600
+ function snapshotTrailingBlankSourceDecisions(messages) {
31601
+ const decisions = new Map;
31602
+ for (const message of messages) {
31603
+ const observed = trailingBlankDecisionForMessage(message);
31604
+ if (observed)
31605
+ decisions.set(...observed);
31606
+ }
31607
+ return decisions;
31608
+ }
31609
+ function findTrailingBlankDecisionCandidates(messages, frozenDecisions, options) {
31610
+ const observedDecisions = options?.sourceDecisions ?? snapshotTrailingBlankSourceDecisions(messages);
31611
+ const visibleIds = new Set(messages.flatMap((message) => typeof message.info.id === "string" ? [message.info.id] : []));
31612
+ const decisions = [];
31613
+ for (const [id, decision] of observedDecisions) {
31614
+ if (!visibleIds.has(id))
31615
+ continue;
31616
+ const frozen = frozenDecisions.get(id);
31617
+ if (frozen !== undefined && (id !== options?.refreshMessageId || frozen === decision || frozen === "strip")) {
31618
+ continue;
31619
+ }
31620
+ decisions.push([id, decision]);
31621
+ }
31622
+ return decisions;
31623
+ }
31624
+ function applyFrozenTrailingBlankDecisions(messages, frozenDecisions) {
31625
+ let mutations = 0;
31626
+ for (let messageIndex = 0;messageIndex < messages.length; messageIndex += 1) {
31627
+ let message = messages[messageIndex];
31628
+ const id = message.info.id;
31629
+ if (message.info.role !== "assistant" || typeof id !== "string")
31630
+ continue;
31631
+ const decision = frozenDecisions.get(id);
31632
+ if (!decision)
31633
+ continue;
31634
+ let lastMeaningfulIndex = message.parts.length - 1;
31635
+ while (lastMeaningfulIndex >= 0 && isSentinelInvisibleTextPart(message.parts[lastMeaningfulIndex])) {
31636
+ lastMeaningfulIndex -= 1;
31637
+ }
31638
+ const replaceParts = (start, deleteCount, insertBlankCount) => {
31639
+ message = { ...message, parts: [...message.parts] };
31640
+ messages[messageIndex] = message;
31641
+ message.parts.splice(start, deleteCount, ...Array.from({ length: insertBlankCount }, () => ({ ...CANONICAL_BLANK_PART })));
31642
+ };
31643
+ if (lastMeaningfulIndex < 0) {
31644
+ if (message.parts.length === 0)
31645
+ continue;
31646
+ if (message.parts.length !== 1 || !isCanonicalBlankPart(message.parts[0])) {
31647
+ mutations += Math.max(1, message.parts.length);
31648
+ replaceParts(0, message.parts.length, 1);
31649
+ }
31650
+ continue;
31651
+ }
31652
+ const trailingCount = message.parts.length - lastMeaningfulIndex - 1;
31653
+ const keepCount = trailingBlankKeepCount(decision);
31654
+ if (keepCount !== undefined) {
31655
+ if (trailingCount === 0)
31656
+ continue;
31657
+ const blankIndex = lastMeaningfulIndex + 1;
31658
+ const suffixIsCanonical = trailingCount === keepCount && message.parts.slice(blankIndex).every(isCanonicalBlankPart);
31659
+ if (!suffixIsCanonical) {
31660
+ mutations += Math.max(1, trailingCount, keepCount);
31661
+ replaceParts(blankIndex, trailingCount, keepCount);
31662
+ }
31663
+ continue;
31664
+ }
31665
+ if (trailingCount === 0)
31666
+ continue;
31667
+ const lastMeaningfulPart = message.parts[lastMeaningfulIndex];
31668
+ if (isRecord(lastMeaningfulPart) && REASONING_PART_TYPES.has(lastMeaningfulPart.type)) {
31669
+ const blankIndex = lastMeaningfulIndex + 1;
31670
+ if (trailingCount !== 1 || !isCanonicalBlankPart(message.parts[blankIndex])) {
31671
+ mutations += Math.max(1, trailingCount);
31672
+ replaceParts(blankIndex, trailingCount, 1);
31673
+ }
31674
+ continue;
31675
+ }
31676
+ mutations += trailingCount;
31677
+ replaceParts(lastMeaningfulIndex + 1, trailingCount, 0);
31678
+ }
31679
+ return mutations;
31680
+ }
31681
+ function findMergedReasoningStripCandidateIds(messages, providerID, options) {
31682
+ if (providerID !== "anthropic")
31683
+ return [];
31684
+ const ids = new Set;
31685
+ for (const entry of planMergedAssistantReasoningStrip(messages, options?.mutationExemptMessage)) {
31686
+ const id = entry.message.info.id;
31687
+ if (typeof id === "string" && id.length > 0)
31688
+ ids.add(id);
31689
+ }
31690
+ return [...ids];
31691
+ }
31692
+ function stripReasoningFromAssistantIds(messages, providerID, messageIds) {
31693
+ if (providerID !== "anthropic" || messageIds.size === 0)
31694
+ return 0;
31695
+ let stripped = 0;
31696
+ for (const message of messages) {
31697
+ const id = message.info.id;
31698
+ if (typeof id !== "string" || !messageIds.has(id))
31699
+ continue;
31700
+ for (let index = 0;index < message.parts.length; index += 1) {
31701
+ const part = message.parts[index];
31702
+ if (!isRecord(part) || !REASONING_PART_TYPES.has(String(part.type)))
31703
+ continue;
31704
+ message.parts[index] = makeSentinel(part);
31705
+ stripped += 1;
31706
+ }
31707
+ }
31708
+ return stripped;
31709
+ }
31710
+ function stripReasoningFromMergedAssistants(messages, providerID, options) {
31711
+ if (providerID !== "anthropic")
31712
+ return 0;
31713
+ let stripped = 0;
31714
+ for (const entry of planMergedAssistantReasoningStrip(messages, options?.mutationExemptMessage)) {
31715
+ if (options?.frozenMessageIds) {
31716
+ const id = entry.message.info.id;
31717
+ if (typeof id !== "string" || !options.frozenMessageIds.has(id))
31718
+ continue;
31719
+ }
31720
+ for (const index of entry.stripIndices) {
31721
+ entry.message.parts[index] = makeSentinel(entry.message.parts[index]);
31722
+ stripped++;
31723
+ }
31724
+ }
31725
+ return stripped;
31726
+ }
31727
+ function stripProcessedImages(messages, frozenIds, options) {
31728
+ const { detect, watermark, messageTagNumbers } = options;
31729
+ let stripped = 0;
31730
+ const newlyStrippedIds = [];
31731
+ let hasAssistantResponse = false;
31732
+ for (let i = messages.length - 1;i >= 0; i--) {
31733
+ const msg = messages[i];
31734
+ if (msg.info.role === "assistant") {
31735
+ hasAssistantResponse = true;
31736
+ continue;
31737
+ }
31738
+ if (msg.info.role !== "user") {
31739
+ continue;
31740
+ }
31741
+ const id = typeof msg.info.id === "string" ? msg.info.id : undefined;
31742
+ const inFrozen = id !== undefined && frozenIds.has(id);
31743
+ const maxTag = messageTagNumbers.get(msg) ?? 0;
31744
+ const isNewDetection = !inFrozen && detect && hasAssistantResponse && id !== undefined && maxTag <= watermark;
31745
+ if (!inFrozen && !isNewDetection) {
31746
+ continue;
31747
+ }
31748
+ let touchedThisMsg = false;
31749
+ for (let j = 0;j < msg.parts.length; j++) {
31750
+ const part = msg.parts[j];
31751
+ if (!isRecord(part) || part.type !== "file") {
31752
+ continue;
31753
+ }
31754
+ if (typeof part.mime !== "string" || !part.mime.startsWith("image/")) {
31755
+ continue;
31756
+ }
31757
+ if (typeof part.url === "string" && part.url.startsWith("data:") && part.url.length > 200) {
31758
+ msg.parts[j] = makeSentinel(part);
31759
+ stripped++;
31760
+ touchedThisMsg = true;
31761
+ }
31762
+ }
31763
+ if (touchedThisMsg && isNewDetection && id !== undefined) {
31764
+ newlyStrippedIds.push(id);
31765
+ }
31766
+ }
31767
+ return { stripped, newlyStrippedIds };
31768
+ }
31769
+
31770
+ // src/hooks/magic-context/transform-context-state.ts
31771
+ function loadPersistedUsageWatermark(db, sessionId) {
31772
+ const result = db.prepare("SELECT last_response_time FROM session_meta WHERE session_id = ?").get(sessionId);
31773
+ if (result === null || typeof result !== "object")
31774
+ return null;
31775
+ const lastResponseTime = result.last_response_time;
31776
+ return typeof lastResponseTime === "number" ? lastResponseTime : null;
31777
+ }
31778
+ function loadContextUsage(contextUsageMap, db, sessionId) {
31779
+ const contextUsageEntry = contextUsageMap.get(sessionId);
31780
+ try {
31781
+ const persistedLastResponseTime = loadPersistedUsageWatermark(db, sessionId);
31782
+ const cachedLastResponseTime = contextUsageEntry?.lastResponseTime ?? contextUsageEntry?.updatedAt;
31783
+ if (contextUsageEntry && contextUsageEntry.lastResponseTime === undefined && (persistedLastResponseTime === null || persistedLastResponseTime === 0)) {
31784
+ return contextUsageEntry.usage;
31785
+ }
31786
+ if (contextUsageEntry && cachedLastResponseTime === persistedLastResponseTime) {
31787
+ return contextUsageEntry.usage;
31788
+ }
31789
+ const persisted = loadPersistedUsage(db, sessionId);
31790
+ if (persisted) {
31791
+ contextUsageMap.set(sessionId, {
31792
+ ...persisted,
31793
+ lastResponseTime: persistedLastResponseTime ?? persisted.updatedAt,
31794
+ hasUsageTokens: false
31795
+ });
31796
+ return persisted.usage;
31797
+ }
31798
+ contextUsageMap.delete(sessionId);
31799
+ } catch (error) {
31800
+ sessionLog(sessionId, "transform failed loading persisted usage:", error);
31801
+ return contextUsageEntry?.usage ?? { percentage: 0, inputTokens: 0 };
31802
+ }
31803
+ return { percentage: 0, inputTokens: 0 };
31804
+ }
31805
+ function resolveSchedulerDecision(scheduler, sessionMeta, contextUsage, sessionId, modelKey, contextLimit) {
31806
+ try {
31807
+ const schedulerDecision = scheduler.shouldExecute(sessionMeta, contextUsage, undefined, sessionId, modelKey, contextLimit);
31808
+ sessionLog(sessionId, `transform scheduler: percentage=${contextUsage.percentage.toFixed(1)}% inputTokens=${contextUsage.inputTokens} cacheTtl=${sessionMeta.cacheTtl} lastResponseTime=${sessionMeta.lastResponseTime} decision=${schedulerDecision}`);
31809
+ return schedulerDecision;
31810
+ } catch (error) {
31811
+ sessionLog(sessionId, "transform scheduler failed; defaulting to defer:", error);
31812
+ return "defer";
31813
+ }
31814
+ }
31815
+
31816
+ // src/hooks/magic-context/channel2-cycle.ts
31817
+ function rearmChannel2AfterCoverageAdvancingHardFold(input) {
31818
+ if (input.compactionOff || !input.foldExecuted || input.previousCoverage === null || input.currentCoverage === null || input.currentCoverage <= input.previousCoverage) {
31819
+ return false;
31820
+ }
31821
+ return casChannel2NudgeState(input.db, input.sessionId, "delivered", "");
31822
+ }
31823
+ function rearmChannel2AfterMeasuredCollapse(input) {
31824
+ if (!input.baseline.evaluable || input.baseline.generationInvalidated)
31825
+ return false;
31826
+ if (effectiveTailHygiene(input.baseline).u >= CHANNEL1_FLOOR_TOKENS)
31827
+ return false;
31828
+ return casChannel2NudgeState(input.db, input.sessionId, "delivered", "");
31829
+ }
31830
+
31831
+ // src/hooks/magic-context/drop-stale-reduce-calls.ts
31832
+ var STALE_TOOL_NAMES = new Set(["ctx_reduce"]);
31833
+ function isReduceToolPart(part) {
31834
+ if (!isRecord(part))
31835
+ return false;
31836
+ if (part.type === "tool" && typeof part.tool === "string" && STALE_TOOL_NAMES.has(part.tool))
31837
+ return true;
31838
+ if (part.type === "tool-invocation" && typeof part.toolName === "string" && STALE_TOOL_NAMES.has(part.toolName))
31839
+ return true;
31840
+ if (part.type === "tool_use" && typeof part.name === "string" && STALE_TOOL_NAMES.has(part.name))
31841
+ return true;
31842
+ return false;
31843
+ }
31844
+ function hasAnyMeaningfulPart(parts) {
31845
+ for (const part of parts) {
31846
+ if (!isRecord(part))
31847
+ continue;
31848
+ if (part.type === "text" && typeof part.text === "string" && part.text.trim().length > 0)
31849
+ return true;
31850
+ if (part.type === "thinking" || part.type === "reasoning" || part.type === "redacted_thinking")
31851
+ continue;
31852
+ if (part.type === "meta" || part.type === "step-start" || part.type === "step-finish")
31853
+ continue;
31854
+ if (part.type !== "tool" || !isReduceToolPart(part))
31855
+ return true;
31856
+ }
31857
+ return false;
31858
+ }
31859
+ function messageHasReducePart(message) {
31860
+ for (const part of message.parts) {
31861
+ if (isSentinel(part))
31862
+ continue;
31863
+ if (isReduceToolPart(part))
31864
+ return true;
31865
+ }
31866
+ return false;
31867
+ }
31868
+ function sentinelizeReduceParts(message) {
31869
+ let touched = false;
31870
+ for (let j = 0;j < message.parts.length; j++) {
31871
+ const part = message.parts[j];
31872
+ if (isSentinel(part))
31873
+ continue;
31874
+ if (isReduceToolPart(part)) {
31875
+ message.parts[j] = makeSentinel(part);
31876
+ touched = true;
31877
+ }
31878
+ }
31879
+ if (touched && !hasAnyMeaningfulPart(message.parts)) {
31880
+ message.parts.length = 0;
31881
+ message.parts.push(makeSentinel(undefined));
30934
31882
  }
30935
31883
  return touched;
30936
31884
  }
@@ -31121,699 +32069,146 @@ function applyHeuristicCleanup(sessionId, db, targets, messageTagNumbers, config
31121
32069
  }
31122
32070
  const fingerprintGroups = new Map;
31123
32071
  for (const [compositeKey, fingerprint] of toolFingerprints) {
31124
- const tag = tagsByCompositeKey.get(compositeKey);
31125
- if (!tag || tag.tagNumber > protectedCutoff)
31126
- continue;
31127
- const group = fingerprintGroups.get(fingerprint) ?? [];
31128
- group.push(tag);
31129
- fingerprintGroups.set(fingerprint, group);
31130
- }
31131
- db.transaction(() => {
31132
- for (const [, group] of fingerprintGroups) {
31133
- if (group.length <= 1)
31134
- continue;
31135
- group.sort((a, b) => a.tagNumber - b.tagNumber);
31136
- for (let i = 0;i < group.length - 1; i++) {
31137
- const tag = group[i];
31138
- const target = targets.get(tag.tagNumber);
31139
- const result = target?.drop?.() ?? "absent";
31140
- if (result === "incomplete")
31141
- continue;
31142
- updateTagDropMode(db, sessionId, tag.tagNumber, "full");
31143
- updateTagStatus(db, sessionId, tag.tagNumber, "dropped");
31144
- if (result === "removed" || result === "truncated") {
31145
- deduplicatedTools++;
31146
- }
31147
- }
31148
- }
31149
- })();
31150
- }
31151
- if (droppedTools > 0 || deduplicatedTools > 0 || droppedInjections > 0) {
31152
- sessionLog(sessionId, `heuristic cleanup: dropped ${droppedTools} tool tags, deduplicated ${deduplicatedTools} tool calls, dropped ${droppedInjections} system injections`);
31153
- }
31154
- let compressedTextTags = 0;
31155
- let mutatedTextTags = 0;
31156
- if (config.routine !== false && config.caveman?.enabled) {
31157
- const cavemanResult = applyCavemanCleanup(sessionId, db, targets, tags, {
31158
- enabled: true,
31159
- minChars: config.caveman.minChars,
31160
- protectedTags: config.protectedTags
31161
- });
31162
- compressedTextTags = cavemanResult.compressedToLite + cavemanResult.compressedToFull + cavemanResult.compressedToUltra;
31163
- mutatedTextTags = cavemanResult.mutatedTextTags;
31164
- }
31165
- return {
31166
- droppedTools,
31167
- deduplicatedTools,
31168
- droppedInjections,
31169
- emergencyDroppedTools,
31170
- emergencyReclaimedTokens,
31171
- compressedTextTags,
31172
- mutatedTextTags
31173
- };
31174
- }
31175
- function extractToolInfo(part) {
31176
- if (part.type === "tool" && typeof part.tool === "string" && DEDUP_SAFE_TOOLS.has(part.tool)) {
31177
- const state = typeof part.state === "object" && part.state !== null ? part.state : {};
31178
- return { toolName: part.tool, args: state.input ?? {} };
31179
- }
31180
- if (part.type === "tool-invocation" && typeof part.toolName === "string" && DEDUP_SAFE_TOOLS.has(part.toolName)) {
31181
- return { toolName: part.toolName, args: part.args ?? {} };
31182
- }
31183
- if (part.type === "tool_use" && typeof part.name === "string" && DEDUP_SAFE_TOOLS.has(part.name)) {
31184
- return { toolName: part.name, args: part.input ?? {} };
31185
- }
31186
- return null;
31187
- }
31188
- function buildToolFingerprints(messages) {
31189
- const fingerprints = new Map;
31190
- for (const message of messages) {
31191
- if (message.info.role !== "assistant")
31192
- continue;
31193
- const ownerMsgId = typeof message.info.id === "string" ? message.info.id : null;
31194
- if (!ownerMsgId)
31195
- continue;
31196
- for (const part of message.parts) {
31197
- const record = part;
31198
- const info = extractToolInfo(record);
31199
- if (!info)
31200
- continue;
31201
- const callId = extractCallId(record);
31202
- if (!callId)
31203
- continue;
31204
- try {
31205
- const fingerprint = `${ownerMsgId}:${info.toolName}:${JSON.stringify(info.args)}`;
31206
- const compositeKey = `${ownerMsgId}\x00${callId}`;
31207
- fingerprints.set(compositeKey, fingerprint);
31208
- } catch {}
31209
- }
31210
- }
31211
- return fingerprints;
31212
- }
31213
- function extractCallId(part) {
31214
- if (part.type === "tool" && typeof part.callID === "string")
31215
- return part.callID;
31216
- if (part.type === "tool-invocation" && typeof part.callID === "string")
31217
- return part.callID;
31218
- if (part.type === "tool_use" && typeof part.id === "string")
31219
- return part.id;
31220
- return null;
31221
- }
31222
-
31223
- // src/hooks/magic-context/note-visibility.ts
31224
- var NOTE_TOOL_NAMES = new Set(["ctx_note"]);
31225
- var READ_ACTION = "read";
31226
- function hasVisibleNoteReadCall(messages) {
31227
- for (let i = messages.length - 1;i >= 0; i -= 1) {
31228
- const parts = messages[i]?.parts;
31229
- if (!Array.isArray(parts))
31230
- continue;
31231
- for (const part of parts) {
31232
- if (isSentinel(part))
31233
- continue;
31234
- if (isVisibleNoteReadPart(part))
31235
- return true;
31236
- }
31237
- }
31238
- return false;
31239
- }
31240
- function isVisibleNoteReadPart(part) {
31241
- if (!isRecord(part))
31242
- return false;
31243
- if (part.type === "tool" && typeof part.tool === "string" && NOTE_TOOL_NAMES.has(part.tool)) {
31244
- const state = part.state;
31245
- if (isRecord(state) && isRecord(state.input)) {
31246
- return state.input.action === READ_ACTION;
31247
- }
31248
- return false;
31249
- }
31250
- if (part.type === "tool_use" && typeof part.name === "string" && NOTE_TOOL_NAMES.has(part.name)) {
31251
- if (isRecord(part.input)) {
31252
- return part.input.action === READ_ACTION;
31253
- }
31254
- return false;
31255
- }
31256
- if (part.type === "tool-invocation" && typeof part.toolName === "string" && NOTE_TOOL_NAMES.has(part.toolName)) {
31257
- const argsCandidate = part.args ?? part.input;
31258
- if (isRecord(argsCandidate)) {
31259
- return argsCandidate.action === READ_ACTION;
31260
- }
31261
- return false;
31262
- }
31263
- return false;
31264
- }
31265
-
31266
- // src/hooks/magic-context/strip-content.ts
31267
- var DROPPED_PLACEHOLDER_PATTERN = /^\[dropped §\d+§\]$/;
31268
- var TAG_PREFIX_PATTERN = /^§\d+§\s*/;
31269
- var SYSTEM_INJECTION_PATTERNS = [
31270
- /^<!-- OMO_INTERNAL_INITIATOR -->$/,
31271
- /^<system-reminder>[\s\S]*<\/system-reminder>$/,
31272
- /^\[SYSTEM DIRECTIVE:/,
31273
- /^\[Category\+Skill Reminder\]/,
31274
- /^\[EDIT ERROR - IMMEDIATE ACTION REQUIRED\]/,
31275
- /^\[task CALL FAILED/,
31276
- /^\[EMERGENCY CONTEXT WINDOW WARNING\]/
31277
- ];
31278
- function isSystemInjectedText(text) {
31279
- const stripped = text.trim().replace(TAG_PREFIX_PATTERN, "").trim();
31280
- if (stripped.length === 0)
31281
- return false;
31282
- return SYSTEM_INJECTION_PATTERNS.some((pattern) => pattern.test(stripped));
31283
- }
31284
- function stripSystemInjectedMessages(messages, protectedTailStart, providerID) {
31285
- let stripped = 0;
31286
- const sentineledIds = [];
31287
- for (let i = 0;i < messages.length; i++) {
31288
- if (i >= protectedTailStart)
31289
- continue;
31290
- const msg = messages[i];
31291
- if (msg.parts.length === 0)
31292
- continue;
31293
- if (msg.info.role === "user")
31294
- continue;
31295
- if (msg.parts.length === 1 && isSentinel(msg.parts[0]))
31296
- continue;
31297
- let hasContentPart = false;
31298
- let allContentIsSystemInjection = true;
31299
- for (const part of msg.parts) {
31300
- if (!isRecord(part))
31301
- continue;
31302
- const partType = part.type;
31303
- if (METADATA_PART_TYPES.has(partType))
31304
- continue;
31305
- if (part.ignored === true)
31306
- continue;
31307
- if (partType === "tool") {
31308
- allContentIsSystemInjection = false;
31309
- break;
31310
- }
31311
- if (partType === "text" && typeof part.text === "string") {
31312
- hasContentPart = true;
31313
- if (!isSystemInjectedText(part.text)) {
31314
- allContentIsSystemInjection = false;
31315
- break;
31316
- }
31317
- continue;
31318
- }
31319
- allContentIsSystemInjection = false;
31320
- break;
31321
- }
31322
- if (hasContentPart && allContentIsSystemInjection) {
31323
- msg.parts.length = 0;
31324
- msg.parts.push(makeWholeMessageSentinel(providerID));
31325
- stripped++;
31326
- if (typeof msg.info.id === "string")
31327
- sentineledIds.push(msg.info.id);
31328
- }
31329
- }
31330
- return { stripped, sentineledIds };
31331
- }
31332
- var METADATA_PART_TYPES = new Set([
31333
- "step-start",
31334
- "step-finish",
31335
- "snapshot",
31336
- "patch",
31337
- "agent",
31338
- "retry",
31339
- "subtask",
31340
- "compaction"
31341
- ]);
31342
- function stripDroppedPlaceholderMessages(messages, providerID) {
31343
- let stripped = 0;
31344
- const sentineledIds = [];
31345
- for (let i = 0;i < messages.length; i++) {
31346
- const msg = messages[i];
31347
- if (msg.parts.length === 0)
31348
- continue;
31349
- if (msg.info.role === "user")
31350
- continue;
31351
- if (msg.parts.length === 1 && isSentinel(msg.parts[0]))
31352
- continue;
31353
- let hasContentPart = false;
31354
- let hasNonDroppedContent = false;
31355
- for (const part of msg.parts) {
31356
- if (!isRecord(part))
31357
- continue;
31358
- const partType = part.type;
31359
- if (METADATA_PART_TYPES.has(partType))
31360
- continue;
31361
- if (partType === "tool") {
31362
- hasNonDroppedContent = true;
31363
- break;
31364
- }
31365
- if (partType === "text" && typeof part.text === "string") {
31366
- hasContentPart = true;
31367
- const trimmed = part.text.trim();
31368
- if (trimmed.length === 0)
31369
- continue;
31370
- if (!trimmed.includes("[dropped §")) {
31371
- hasNonDroppedContent = true;
31372
- break;
31373
- }
31374
- const allSegmentsDropped = trimmed.split(/(?=\[dropped §)/).filter((s) => s.trim().length > 0).every((segment) => DROPPED_PLACEHOLDER_PATTERN.test(segment.trim()));
31375
- if (!allSegmentsDropped) {
31376
- hasNonDroppedContent = true;
31377
- break;
31378
- }
31379
- continue;
31380
- }
31381
- if (partType === "reasoning" && typeof part.text === "string") {
31382
- hasContentPart = true;
31383
- const trimmed = part.text.trim();
31384
- if (trimmed.length === 0)
31385
- continue;
31386
- if (!trimmed.includes("[dropped §")) {
31387
- hasNonDroppedContent = true;
31388
- break;
31389
- }
31390
- const allSegmentsDropped = trimmed.split(/(?=\[dropped §)/).filter((s) => s.trim().length > 0).every((segment) => DROPPED_PLACEHOLDER_PATTERN.test(segment.trim()));
31391
- if (!allSegmentsDropped) {
31392
- hasNonDroppedContent = true;
31393
- break;
31394
- }
31395
- continue;
31396
- }
31397
- hasNonDroppedContent = true;
31398
- break;
31399
- }
31400
- if (hasContentPart && !hasNonDroppedContent) {
31401
- msg.parts.length = 0;
31402
- msg.parts.push(makeWholeMessageSentinel(providerID));
31403
- stripped++;
31404
- if (typeof msg.info.id === "string")
31405
- sentineledIds.push(msg.info.id);
31406
- }
31407
- }
31408
- return { stripped, sentineledIds };
31409
- }
31410
- function replayClearedReasoning(messages, reasoningByMessage, messageTagNumbers, persistedWatermark) {
31411
- if (persistedWatermark <= 0)
31412
- return 0;
31413
- let cleared = 0;
31414
- for (const message of messages) {
31415
- const msgTag = messageTagNumbers.get(message) ?? 0;
31416
- if (msgTag === 0 || msgTag > persistedWatermark)
31417
- continue;
31418
- const parts = reasoningByMessage.get(message);
31419
- if (!parts)
31420
- continue;
31421
- for (const tp of parts) {
31422
- if (tp.thinking !== undefined && tp.thinking !== "[cleared]") {
31423
- tp.thinking = "[cleared]";
31424
- cleared++;
31425
- }
31426
- if (tp.text !== undefined && tp.text !== "[cleared]") {
31427
- tp.text = "[cleared]";
31428
- cleared++;
31429
- }
31430
- }
31431
- }
31432
- return cleared;
31433
- }
31434
- function replayStrippedInlineThinking(messages, messageTagNumbers, persistedWatermark) {
31435
- if (persistedWatermark <= 0)
31436
- return 0;
31437
- let stripped = 0;
31438
- for (const message of messages) {
31439
- if (message.info.role !== "assistant")
31440
- continue;
31441
- const msgTag = messageTagNumbers.get(message) ?? 0;
31442
- if (msgTag === 0 || msgTag > persistedWatermark)
31443
- continue;
31444
- for (const part of message.parts) {
31445
- if (!isRecord(part) || part.type !== "text" || typeof part.text !== "string")
31446
- continue;
31447
- if (!part.text.includes("<think"))
31448
- continue;
31449
- const cleaned = part.text.replace(INLINE_THINKING_PATTERN, "");
31450
- if (cleaned !== part.text) {
31451
- part.text = cleaned;
31452
- stripped++;
31453
- }
31454
- }
31455
- }
31456
- return stripped;
31457
- }
31458
- function clearOldReasoning(messages, reasoningByMessage, messageTagNumbers, clearReasoningAge) {
31459
- const maxTag = findMaxTag(messageTagNumbers);
31460
- if (maxTag === 0)
31461
- return 0;
31462
- const ageCutoff = maxTag - clearReasoningAge;
31463
- let cleared = 0;
31464
- for (const message of messages) {
31465
- const msgTag = messageTagNumbers.get(message) ?? 0;
31466
- if (msgTag === 0 || msgTag > ageCutoff)
31467
- continue;
31468
- const parts = reasoningByMessage.get(message);
31469
- if (!parts)
31470
- continue;
31471
- for (const tp of parts) {
31472
- if (tp.thinking !== undefined && tp.thinking !== "[cleared]") {
31473
- tp.thinking = "[cleared]";
31474
- cleared++;
31475
- }
31476
- if (tp.text !== undefined && tp.text !== "[cleared]") {
31477
- tp.text = "[cleared]";
31478
- cleared++;
31479
- }
31480
- }
31481
- }
31482
- return cleared;
31483
- }
31484
- function findMaxTag(messageTagNumbers) {
31485
- let max = 0;
31486
- for (const tag of messageTagNumbers.values()) {
31487
- if (tag > max)
31488
- max = tag;
31489
- }
31490
- return max;
31491
- }
31492
- var CLEARED_REASONING_TYPES = new Set(["thinking", "reasoning"]);
31493
- function stripClearedReasoning(messages) {
31494
- let stripped = 0;
31495
- for (const message of messages) {
31496
- if (message.info.role !== "assistant")
31497
- continue;
31498
- for (let i = 0;i < message.parts.length; i++) {
31499
- const part = message.parts[i];
31500
- if (!isRecord(part))
31501
- continue;
31502
- const partType = part.type;
31503
- if (!CLEARED_REASONING_TYPES.has(partType))
31504
- continue;
31505
- if (!("thinking" in part) && !("text" in part))
31506
- continue;
31507
- const thinking = "thinking" in part ? part.thinking : undefined;
31508
- const text = "text" in part ? part.text : undefined;
31509
- const isCleared = (thinking === undefined || thinking === "[cleared]") && (text === undefined || text === "[cleared]");
31510
- if (!isCleared)
31511
- continue;
31512
- message.parts[i] = makeSentinel(part);
31513
- stripped++;
31514
- }
31515
- }
31516
- return stripped;
31517
- }
31518
- var INLINE_THINKING_PATTERN = /<(?:thinking|think)>[\s\S]*?<\/(?:thinking|think)>\s*/g;
31519
- function stripInlineThinking(messages, messageTagNumbers, clearReasoningAge) {
31520
- const maxTag = findMaxTag(messageTagNumbers);
31521
- if (maxTag === 0)
31522
- return 0;
31523
- const ageCutoff = maxTag - clearReasoningAge;
31524
- let stripped = 0;
31525
- for (const message of messages) {
31526
- if (message.info.role !== "assistant")
31527
- continue;
31528
- const msgTag = messageTagNumbers.get(message) ?? 0;
31529
- if (msgTag === 0 || msgTag > ageCutoff)
31530
- continue;
31531
- for (const part of message.parts) {
31532
- if (!isRecord(part) || part.type !== "text" || typeof part.text !== "string")
31533
- continue;
31534
- const cleaned = part.text.replace(INLINE_THINKING_PATTERN, "");
31535
- if (cleaned !== part.text) {
31536
- part.text = cleaned;
31537
- stripped++;
31538
- }
31539
- }
31540
- }
31541
- return stripped;
31542
- }
31543
- var REASONING_IGNORED_PART_TYPES = new Set([
31544
- "step-start",
31545
- "step-finish",
31546
- "snapshot",
31547
- "patch",
31548
- "agent",
31549
- "retry",
31550
- "subtask",
31551
- "compaction"
31552
- ]);
31553
- var REASONING_PART_TYPES = new Set(["reasoning", "thinking", "redacted_thinking"]);
31554
- function hasReasoningReplayContent(message) {
31555
- return message.parts.some((part) => {
31556
- if (!isRecord(part) || part.ignored === true)
31557
- return false;
31558
- const type = typeof part.type === "string" ? part.type : "";
31559
- if (REASONING_IGNORED_PART_TYPES.has(type))
31560
- return false;
31561
- return type !== "text" || typeof part.text !== "string" || part.text.trim().length > 0;
31562
- });
31563
- }
31564
- function findLatestAssistantReasoningMutationExemptMessage(messages) {
31565
- for (let index = messages.length - 1;index >= 0; index -= 1) {
31566
- const message = messages[index];
31567
- if (message.info.role === "assistant" && hasReasoningReplayContent(message))
31568
- return message;
31569
- }
31570
- return;
31571
- }
31572
- function planMergedAssistantReasoningStrip(messages, mutationExemptMessage) {
31573
- const plan = [];
31574
- let prevRole;
31575
- let keptReasoningInRun = false;
31576
- for (const message of messages) {
31577
- const role = message.info.role;
31578
- if (role !== "assistant") {
31579
- prevRole = role;
31580
- keptReasoningInRun = false;
31581
- continue;
31582
- }
31583
- const firstInRun = prevRole !== "assistant";
31584
- if (firstInRun)
31585
- keptReasoningInRun = false;
31586
- if (message === mutationExemptMessage) {
31587
- prevRole = role;
31588
- continue;
31589
- }
31590
- let keepIndex = -1;
31591
- if (firstInRun && !keptReasoningInRun) {
31592
- for (let i = 0;i < message.parts.length; i++) {
31593
- const part = message.parts[i];
31594
- if (!isRecord(part))
31595
- continue;
31596
- const partType = part.type;
31597
- if (REASONING_IGNORED_PART_TYPES.has(partType))
31598
- continue;
31599
- if (part.ignored === true)
31600
- continue;
31601
- if (isSentinel(part))
31602
- continue;
31603
- if (partType === "text" && typeof part.text === "string" && part.text.trim() === "") {
31604
- continue;
31605
- }
31606
- if (REASONING_PART_TYPES.has(partType)) {
31607
- keepIndex = i;
31608
- }
31609
- break;
31610
- }
31611
- }
31612
- const stripIndices = [];
31613
- for (let i = 0;i < message.parts.length; i++) {
31614
- const part = message.parts[i];
31615
- if (!isRecord(part))
31616
- continue;
31617
- if (!REASONING_PART_TYPES.has(part.type))
31618
- continue;
31619
- if (part.cache_control !== undefined)
31620
- continue;
31621
- if (i === keepIndex) {
31622
- keptReasoningInRun = true;
32072
+ const tag = tagsByCompositeKey.get(compositeKey);
32073
+ if (!tag || tag.tagNumber > protectedCutoff)
31623
32074
  continue;
31624
- }
31625
- stripIndices.push(i);
32075
+ const group = fingerprintGroups.get(fingerprint) ?? [];
32076
+ group.push(tag);
32077
+ fingerprintGroups.set(fingerprint, group);
31626
32078
  }
31627
- if (stripIndices.length > 0)
31628
- plan.push({ message, stripIndices });
31629
- prevRole = role;
31630
- }
31631
- return plan;
31632
- }
31633
- var MAX_FROZEN_TRAILING_BLANKS = 1e4;
31634
- var CANONICAL_BLANK_PART = { type: "text", text: "" };
31635
- function isSentinelInvisibleTextPart(part) {
31636
- return isRecord(part) && part.type === "text" && typeof part.text === "string" && part.text.trim() === "";
31637
- }
31638
- function isCanonicalBlankPart(part) {
31639
- return isRecord(part) && Object.keys(part).length === 2 && part.type === "text" && part.text === "";
31640
- }
31641
- function trailingBlankKeepCount(decision) {
31642
- if (decision === "keep")
31643
- return 1;
31644
- if (!decision.startsWith("keep:"))
31645
- return;
31646
- const countText = decision.slice("keep:".length);
31647
- if (!/^[1-9]\d*$/.test(countText))
31648
- return;
31649
- const count = Number(countText);
31650
- return Number.isSafeInteger(count) && count > 0 && count <= MAX_FROZEN_TRAILING_BLANKS ? count : undefined;
31651
- }
31652
- function trailingBlankDecisionForMessage(message) {
31653
- const id = message.info.id;
31654
- if (message.info.role !== "assistant" || typeof id !== "string" || id.length === 0) {
31655
- return;
32079
+ db.transaction(() => {
32080
+ for (const [, group] of fingerprintGroups) {
32081
+ if (group.length <= 1)
32082
+ continue;
32083
+ group.sort((a, b) => a.tagNumber - b.tagNumber);
32084
+ for (let i = 0;i < group.length - 1; i++) {
32085
+ const tag = group[i];
32086
+ const target = targets.get(tag.tagNumber);
32087
+ const result = target?.drop?.() ?? "absent";
32088
+ if (result === "incomplete")
32089
+ continue;
32090
+ updateTagDropMode(db, sessionId, tag.tagNumber, "full");
32091
+ updateTagStatus(db, sessionId, tag.tagNumber, "dropped");
32092
+ if (result === "removed" || result === "truncated") {
32093
+ deduplicatedTools++;
32094
+ }
32095
+ }
32096
+ }
32097
+ })();
31656
32098
  }
31657
- let trailingCount = 0;
31658
- while (trailingCount < message.parts.length && isSentinelInvisibleTextPart(message.parts[message.parts.length - trailingCount - 1])) {
31659
- trailingCount += 1;
32099
+ if (droppedTools > 0 || deduplicatedTools > 0 || droppedInjections > 0) {
32100
+ sessionLog(sessionId, `heuristic cleanup: dropped ${droppedTools} tool tags, deduplicated ${deduplicatedTools} tool calls, dropped ${droppedInjections} system injections`);
31660
32101
  }
31661
- if (trailingCount > MAX_FROZEN_TRAILING_BLANKS && trailingCount < message.parts.length) {
31662
- return;
32102
+ let compressedTextTags = 0;
32103
+ let mutatedTextTags = 0;
32104
+ if (config.routine !== false && config.caveman?.enabled) {
32105
+ const cavemanResult = applyCavemanCleanup(sessionId, db, targets, tags, {
32106
+ enabled: true,
32107
+ minChars: config.caveman.minChars,
32108
+ protectedTags: config.protectedTags
32109
+ });
32110
+ compressedTextTags = cavemanResult.compressedToLite + cavemanResult.compressedToFull + cavemanResult.compressedToUltra;
32111
+ mutatedTextTags = cavemanResult.mutatedTextTags;
31663
32112
  }
31664
- const decision = message.parts.length === 0 || trailingCount === message.parts.length ? "keep" : trailingCount === 0 ? "strip" : trailingCount === 1 ? "keep" : `keep:${trailingCount}`;
31665
- return [id, decision];
32113
+ return {
32114
+ droppedTools,
32115
+ deduplicatedTools,
32116
+ droppedInjections,
32117
+ emergencyDroppedTools,
32118
+ emergencyReclaimedTokens,
32119
+ compressedTextTags,
32120
+ mutatedTextTags
32121
+ };
31666
32122
  }
31667
- function snapshotTrailingBlankSourceDecisions(messages) {
31668
- const decisions = new Map;
31669
- for (const message of messages) {
31670
- const observed = trailingBlankDecisionForMessage(message);
31671
- if (observed)
31672
- decisions.set(...observed);
32123
+ function extractToolInfo(part) {
32124
+ if (part.type === "tool" && typeof part.tool === "string" && DEDUP_SAFE_TOOLS.has(part.tool)) {
32125
+ const state = typeof part.state === "object" && part.state !== null ? part.state : {};
32126
+ return { toolName: part.tool, args: state.input ?? {} };
31673
32127
  }
31674
- return decisions;
31675
- }
31676
- function findTrailingBlankDecisionCandidates(messages, frozenDecisions, options) {
31677
- const observedDecisions = options?.sourceDecisions ?? snapshotTrailingBlankSourceDecisions(messages);
31678
- const visibleIds = new Set(messages.flatMap((message) => typeof message.info.id === "string" ? [message.info.id] : []));
31679
- const decisions = [];
31680
- for (const [id, decision] of observedDecisions) {
31681
- if (!visibleIds.has(id))
31682
- continue;
31683
- const frozen = frozenDecisions.get(id);
31684
- if (frozen !== undefined && (id !== options?.refreshMessageId || frozen === decision)) {
31685
- continue;
31686
- }
31687
- decisions.push([id, decision]);
32128
+ if (part.type === "tool-invocation" && typeof part.toolName === "string" && DEDUP_SAFE_TOOLS.has(part.toolName)) {
32129
+ return { toolName: part.toolName, args: part.args ?? {} };
31688
32130
  }
31689
- return decisions;
32131
+ if (part.type === "tool_use" && typeof part.name === "string" && DEDUP_SAFE_TOOLS.has(part.name)) {
32132
+ return { toolName: part.name, args: part.input ?? {} };
32133
+ }
32134
+ return null;
31690
32135
  }
31691
- function applyFrozenTrailingBlankDecisions(messages, newestAssistantId, frozenDecisions) {
31692
- let mutations = 0;
31693
- for (let messageIndex = 0;messageIndex < messages.length; messageIndex += 1) {
31694
- let message = messages[messageIndex];
31695
- const id = message.info.id;
31696
- if (message.info.role !== "assistant" || typeof id !== "string")
32136
+ function buildToolFingerprints(messages) {
32137
+ const fingerprints = new Map;
32138
+ for (const message of messages) {
32139
+ if (message.info.role !== "assistant")
31697
32140
  continue;
31698
- const decision = frozenDecisions.get(id);
31699
- if (!decision)
32141
+ const ownerMsgId = typeof message.info.id === "string" ? message.info.id : null;
32142
+ if (!ownerMsgId)
31700
32143
  continue;
31701
- let lastMeaningfulIndex = message.parts.length - 1;
31702
- while (lastMeaningfulIndex >= 0 && isSentinelInvisibleTextPart(message.parts[lastMeaningfulIndex])) {
31703
- lastMeaningfulIndex -= 1;
31704
- }
31705
- const replaceParts = (start, deleteCount, insertBlankCount) => {
31706
- message = { ...message, parts: [...message.parts] };
31707
- messages[messageIndex] = message;
31708
- message.parts.splice(start, deleteCount, ...Array.from({ length: insertBlankCount }, () => ({ ...CANONICAL_BLANK_PART })));
31709
- };
31710
- if (lastMeaningfulIndex < 0) {
31711
- if (message.parts.length === 0)
32144
+ for (const part of message.parts) {
32145
+ const record = part;
32146
+ const info = extractToolInfo(record);
32147
+ if (!info)
31712
32148
  continue;
31713
- if (message.parts.length !== 1 || !isCanonicalBlankPart(message.parts[0])) {
31714
- mutations += Math.max(1, message.parts.length);
31715
- replaceParts(0, message.parts.length, 1);
31716
- }
31717
- continue;
31718
- }
31719
- const trailingCount = message.parts.length - lastMeaningfulIndex - 1;
31720
- const keepCount = trailingBlankKeepCount(decision);
31721
- if (keepCount !== undefined) {
31722
- if (trailingCount === 0)
32149
+ const callId = extractCallId(record);
32150
+ if (!callId)
31723
32151
  continue;
31724
- const blankIndex = lastMeaningfulIndex + 1;
31725
- const suffixIsCanonical = trailingCount === keepCount && message.parts.slice(blankIndex).every(isCanonicalBlankPart);
31726
- if (!suffixIsCanonical) {
31727
- mutations += Math.max(1, trailingCount, keepCount);
31728
- replaceParts(blankIndex, trailingCount, keepCount);
31729
- }
31730
- continue;
31731
- }
31732
- if (id === newestAssistantId || trailingCount === 0)
31733
- continue;
31734
- const lastMeaningfulPart = message.parts[lastMeaningfulIndex];
31735
- if (isRecord(lastMeaningfulPart) && REASONING_PART_TYPES.has(lastMeaningfulPart.type)) {
31736
- const blankIndex = lastMeaningfulIndex + 1;
31737
- if (trailingCount !== 1 || !isCanonicalBlankPart(message.parts[blankIndex])) {
31738
- mutations += Math.max(1, trailingCount);
31739
- replaceParts(blankIndex, trailingCount, 1);
31740
- }
31741
- continue;
32152
+ try {
32153
+ const fingerprint = `${ownerMsgId}:${info.toolName}:${JSON.stringify(info.args)}`;
32154
+ const compositeKey = `${ownerMsgId}\x00${callId}`;
32155
+ fingerprints.set(compositeKey, fingerprint);
32156
+ } catch {}
31742
32157
  }
31743
- mutations += trailingCount;
31744
- replaceParts(lastMeaningfulIndex + 1, trailingCount, 0);
31745
32158
  }
31746
- return mutations;
32159
+ return fingerprints;
31747
32160
  }
31748
- function findMergedReasoningStripCandidateIds(messages, providerID, options) {
31749
- if (providerID !== "anthropic")
31750
- return [];
31751
- const ids = new Set;
31752
- for (const entry of planMergedAssistantReasoningStrip(messages, options?.mutationExemptMessage)) {
31753
- const id = entry.message.info.id;
31754
- if (typeof id === "string" && id.length > 0)
31755
- ids.add(id);
31756
- }
31757
- return [...ids];
32161
+ function extractCallId(part) {
32162
+ if (part.type === "tool" && typeof part.callID === "string")
32163
+ return part.callID;
32164
+ if (part.type === "tool-invocation" && typeof part.callID === "string")
32165
+ return part.callID;
32166
+ if (part.type === "tool_use" && typeof part.id === "string")
32167
+ return part.id;
32168
+ return null;
31758
32169
  }
31759
- function stripReasoningFromMergedAssistants(messages, providerID, options) {
31760
- if (providerID !== "anthropic")
31761
- return 0;
31762
- let stripped = 0;
31763
- for (const entry of planMergedAssistantReasoningStrip(messages, options?.mutationExemptMessage)) {
31764
- if (options?.frozenMessageIds) {
31765
- const id = entry.message.info.id;
31766
- if (typeof id !== "string" || !options.frozenMessageIds.has(id))
32170
+
32171
+ // src/hooks/magic-context/note-visibility.ts
32172
+ var NOTE_TOOL_NAMES = new Set(["ctx_note"]);
32173
+ var READ_ACTION = "read";
32174
+ function hasVisibleNoteReadCall(messages) {
32175
+ for (let i = messages.length - 1;i >= 0; i -= 1) {
32176
+ const parts = messages[i]?.parts;
32177
+ if (!Array.isArray(parts))
32178
+ continue;
32179
+ for (const part of parts) {
32180
+ if (isSentinel(part))
31767
32181
  continue;
31768
- }
31769
- for (const index of entry.stripIndices) {
31770
- entry.message.parts[index] = makeSentinel(entry.message.parts[index]);
31771
- stripped++;
32182
+ if (isVisibleNoteReadPart(part))
32183
+ return true;
31772
32184
  }
31773
32185
  }
31774
- return stripped;
32186
+ return false;
31775
32187
  }
31776
- function stripProcessedImages(messages, frozenIds, options) {
31777
- const { detect, watermark, messageTagNumbers } = options;
31778
- let stripped = 0;
31779
- const newlyStrippedIds = [];
31780
- let hasAssistantResponse = false;
31781
- for (let i = messages.length - 1;i >= 0; i--) {
31782
- const msg = messages[i];
31783
- if (msg.info.role === "assistant") {
31784
- hasAssistantResponse = true;
31785
- continue;
31786
- }
31787
- if (msg.info.role !== "user") {
31788
- continue;
31789
- }
31790
- const id = typeof msg.info.id === "string" ? msg.info.id : undefined;
31791
- const inFrozen = id !== undefined && frozenIds.has(id);
31792
- const maxTag = messageTagNumbers.get(msg) ?? 0;
31793
- const isNewDetection = !inFrozen && detect && hasAssistantResponse && id !== undefined && maxTag <= watermark;
31794
- if (!inFrozen && !isNewDetection) {
31795
- continue;
32188
+ function isVisibleNoteReadPart(part) {
32189
+ if (!isRecord(part))
32190
+ return false;
32191
+ if (part.type === "tool" && typeof part.tool === "string" && NOTE_TOOL_NAMES.has(part.tool)) {
32192
+ const state = part.state;
32193
+ if (isRecord(state) && isRecord(state.input)) {
32194
+ return state.input.action === READ_ACTION;
31796
32195
  }
31797
- let touchedThisMsg = false;
31798
- for (let j = 0;j < msg.parts.length; j++) {
31799
- const part = msg.parts[j];
31800
- if (!isRecord(part) || part.type !== "file") {
31801
- continue;
31802
- }
31803
- if (typeof part.mime !== "string" || !part.mime.startsWith("image/")) {
31804
- continue;
31805
- }
31806
- if (typeof part.url === "string" && part.url.startsWith("data:") && part.url.length > 200) {
31807
- msg.parts[j] = makeSentinel(part);
31808
- stripped++;
31809
- touchedThisMsg = true;
31810
- }
32196
+ return false;
32197
+ }
32198
+ if (part.type === "tool_use" && typeof part.name === "string" && NOTE_TOOL_NAMES.has(part.name)) {
32199
+ if (isRecord(part.input)) {
32200
+ return part.input.action === READ_ACTION;
31811
32201
  }
31812
- if (touchedThisMsg && isNewDetection && id !== undefined) {
31813
- newlyStrippedIds.push(id);
32202
+ return false;
32203
+ }
32204
+ if (part.type === "tool-invocation" && typeof part.toolName === "string" && NOTE_TOOL_NAMES.has(part.toolName)) {
32205
+ const argsCandidate = part.args ?? part.input;
32206
+ if (isRecord(argsCandidate)) {
32207
+ return argsCandidate.action === READ_ACTION;
31814
32208
  }
32209
+ return false;
31815
32210
  }
31816
- return { stripped, newlyStrippedIds };
32211
+ return false;
31817
32212
  }
31818
32213
 
31819
32214
  // src/hooks/magic-context/supersession-reclaim.ts
@@ -32137,14 +32532,15 @@ function createExistingTagResolver(sessionId, tagger, db) {
32137
32532
  return cachedScopedAssignments;
32138
32533
  }
32139
32534
  return {
32140
- resolve(messageId2, type, currentContentId, ordinal) {
32535
+ resolve(messageId2, type, currentContentId, ordinal, options) {
32536
+ const accept = options?.accept ?? (() => true);
32141
32537
  const exactTagId = assignments.get(currentContentId);
32142
- if (exactTagId !== undefined) {
32538
+ if (exactTagId !== undefined && accept(exactTagId)) {
32143
32539
  usedTagNumbers.add(exactTagId);
32144
32540
  return exactTagId;
32145
32541
  }
32146
32542
  const fallback = getScopedAssignments().get(messageId2)?.[type][ordinal];
32147
- if (!fallback || usedTagNumbers.has(fallback.tagNumber)) {
32543
+ if (!fallback || usedTagNumbers.has(fallback.tagNumber) || !accept(fallback.tagNumber)) {
32148
32544
  return;
32149
32545
  }
32150
32546
  updateTagMessageId(db, sessionId, fallback.tagNumber, currentContentId);
@@ -32336,6 +32732,21 @@ function tagMessages(sessionId, messages, tagger, db, options = {}) {
32336
32732
  };
32337
32733
  const ownerByPartKey = new Map;
32338
32734
  const batch = new ToolMutationBatch(messages);
32735
+ const inertWhitespaceTagNumbers = new Set;
32736
+ const inertWhitespaceTagsByMessage = new Map;
32737
+ for (const tag of getInertWhitespaceAssistantTags(db, sessionId)) {
32738
+ inertWhitespaceTagNumbers.add(tag.tagNumber);
32739
+ tagger.bindTag(sessionId, tag.contentId, tag.tagNumber);
32740
+ const scoped = /^(.*):p(\d+)$/.exec(tag.contentId);
32741
+ if (!scoped)
32742
+ continue;
32743
+ const list = inertWhitespaceTagsByMessage.get(scoped[1]) ?? [];
32744
+ list.push({ partIndex: Number(scoped[2]), tagNumber: tag.tagNumber });
32745
+ inertWhitespaceTagsByMessage.set(scoped[1], list);
32746
+ }
32747
+ for (const list of inertWhitespaceTagsByMessage.values()) {
32748
+ list.sort((left, right) => left.partIndex - right.partIndex);
32749
+ }
32339
32750
  const assignments = tagger.getAssignments(sessionId);
32340
32751
  const resolver = createExistingTagResolver(sessionId, tagger, db);
32341
32752
  const tGetSourceContents = performance.now();
@@ -32364,6 +32775,7 @@ function tagMessages(sessionId, messages, tagger, db, options = {}) {
32364
32775
  const messageHasTextPart = message.parts.some(isTextPart);
32365
32776
  let textOrdinal = 0;
32366
32777
  let fileOrdinal = 0;
32778
+ let whitespaceRank = 0;
32367
32779
  for (let partIndex = 0;partIndex < message.parts.length; partIndex += 1) {
32368
32780
  const part = message.parts[partIndex];
32369
32781
  if (isReduceToolPart(part)) {
@@ -32419,7 +32831,47 @@ function tagMessages(sessionId, messages, tagger, db, options = {}) {
32419
32831
  const textPart = part;
32420
32832
  const thinkingParts = messageThinkingParts;
32421
32833
  const contentId = `${messageId2}:p${partIndex}`;
32422
- resolver.resolve(messageId2, "message", contentId, textOrdinal);
32834
+ const whitespaceOnlyAssistant = message.info.role === "assistant" && stripTagPrefix(textPart.text).trim().length === 0;
32835
+ let existingTagId;
32836
+ if (whitespaceOnlyAssistant) {
32837
+ const persistedWhitespaceTag = getTagNumberByMessageId(db, sessionId, contentId);
32838
+ const rankedInertTag = inertWhitespaceTagsByMessage.get(messageId2)?.[whitespaceRank];
32839
+ const canReplayRankedTag = rankedInertTag?.partIndex === partIndex || isThinkingPart(message.parts[partIndex - 1]) || isThinkingPart(message.parts[partIndex + 1]);
32840
+ const inertForThisPart = canReplayRankedTag ? rankedInertTag?.tagNumber : undefined;
32841
+ whitespaceRank += 1;
32842
+ if (inertForThisPart !== undefined) {
32843
+ tagger.bindTag(sessionId, contentId, inertForThisPart);
32844
+ existingTagId = inertForThisPart;
32845
+ } else {
32846
+ existingTagId = resolver.resolve(messageId2, "message", contentId, textOrdinal, { accept: (tagNumber) => tagNumber === persistedWhitespaceTag });
32847
+ }
32848
+ if (existingTagId === undefined) {
32849
+ const persisted = getTagNumberByMessageId(db, sessionId, contentId);
32850
+ if (persisted !== null && !getSourceContents(db, sessionId, [persisted]).has(persisted)) {
32851
+ tagger.bindTag(sessionId, contentId, persisted);
32852
+ existingTagId = persisted;
32853
+ }
32854
+ }
32855
+ if (existingTagId === undefined) {
32856
+ textOrdinal += 1;
32857
+ continue;
32858
+ }
32859
+ const existingTag = getTagById(db, sessionId, existingTagId);
32860
+ if (!existingTag || existingTag.type !== "message") {
32861
+ tagger.unbindTag(sessionId, contentId);
32862
+ textOrdinal += 1;
32863
+ continue;
32864
+ }
32865
+ if (existingTag.status === "active") {
32866
+ markWhitespaceAssistantTagInert(db, sessionId, existingTagId, contentId);
32867
+ inertWhitespaceTagNumbers.add(existingTagId);
32868
+ }
32869
+ } else {
32870
+ const resolved = resolver.resolve(messageId2, "message", contentId, textOrdinal, { accept: (tagNumber) => !inertWhitespaceTagNumbers.has(tagNumber) });
32871
+ if (resolved === undefined && inertWhitespaceTagNumbers.has(tagger.getTag(sessionId, contentId, "message") ?? -1)) {
32872
+ tagger.unbindTag(sessionId, contentId);
32873
+ }
32874
+ }
32423
32875
  const reasoningBytes = textOrdinal === 0 ? getReasoningByteSize(thinkingParts) : 0;
32424
32876
  const reasoningTokens = textOrdinal === 0 ? getReasoningTokenCount(thinkingParts) : 0;
32425
32877
  const _tAssignText = performance.now();
@@ -32689,11 +33141,24 @@ async function applyTodoSynthesis(args) {
32689
33141
  }
32690
33142
  return 0;
32691
33143
  }
33144
+ function replayRustModeBindingMismatchStrips(args) {
33145
+ if (!modelAcceptsEmptyContent(args.resolvedProviderID))
33146
+ return;
33147
+ const recoveryMessageIds = new Set;
33148
+ for (const id of getMergedReasoningStrippedIds(args.db, args.sessionId)) {
33149
+ if (!id.startsWith(THINKING_BINDING_RECOVERY_FROZEN_PREFIX))
33150
+ continue;
33151
+ const messageId2 = id.slice(THINKING_BINDING_RECOVERY_FROZEN_PREFIX.length);
33152
+ if (messageId2.length > 0)
33153
+ recoveryMessageIds.add(messageId2);
33154
+ }
33155
+ stripReasoningFromAssistantIds(args.messages, args.resolvedProviderID, recoveryMessageIds);
33156
+ }
32692
33157
  function runRustModePostprocess(args) {
32693
33158
  if (!args.fullFeatureMode || args.compactionOff)
32694
- return;
33159
+ return { thinkingBindingRecovery: null };
32695
33160
  if (args.messages.some((message) => !message || typeof message !== "object" || !isRecord(message.info))) {
32696
- return;
33161
+ return { thinkingBindingRecovery: null };
32697
33162
  }
32698
33163
  reconcileMarkerRepresentation(args.messages, getPersistedCompactionMarkerState(args.db, args.sessionId), {
32699
33164
  db: args.db,
@@ -32709,21 +33174,80 @@ function runRustModePostprocess(args) {
32709
33174
  appendReminderToUserMessageById(args.messages, decision.messageId, decision.text);
32710
33175
  }
32711
33176
  }
33177
+ const trailingBlankDecisions = new Map;
33178
+ if (modelAcceptsEmptyContent(args.resolvedProviderID)) {
33179
+ try {
33180
+ for (const [id, decision] of getTrailingBlankDecisions(args.db, args.sessionId)) {
33181
+ trailingBlankDecisions.set(id, decision);
33182
+ }
33183
+ const candidates = findTrailingBlankDecisionCandidates(args.messages, trailingBlankDecisions, { sourceDecisions: args.trailingBlankSourceDecisions }).filter(([id]) => id === args.trailingBlankNewestAssistantId);
33184
+ if (candidates.length > 0) {
33185
+ const persisted = addTrailingBlankDecisions(args.db, args.sessionId, candidates, {
33186
+ overwriteMessageId: args.trailingBlankNewestAssistantId
33187
+ });
33188
+ if (persisted) {
33189
+ const committed = getTrailingBlankDecisions(args.db, args.sessionId);
33190
+ for (const [id] of candidates) {
33191
+ const decision = committed.get(id);
33192
+ if (decision)
33193
+ trailingBlankDecisions.set(id, decision);
33194
+ }
33195
+ } else {
33196
+ sessionLog(args.sessionId, "rust trailing blank decision: persistence failed; serving only frozen decisions");
33197
+ }
33198
+ }
33199
+ } catch (error) {
33200
+ sessionLog(args.sessionId, "rust trailing blank decision failed:", error);
33201
+ }
33202
+ }
33203
+ const absorbingStripDecisions = new Map([...trailingBlankDecisions].filter(([, decision]) => decision === "strip"));
33204
+ applyFrozenTrailingBlankDecisions(args.messages, absorbingStripDecisions);
32712
33205
  const currentUserMessageId = findLastUserMessageId(args.messages);
32713
33206
  const noteReadStillVisible = hasVisibleNoteReadCall(args.messages);
32714
33207
  const deferredNoteText = peekNoteNudgeText(args.db, args.sessionId, currentUserMessageId, args.projectPath, noteReadStillVisible);
32715
- if (!deferredNoteText)
32716
- return;
32717
- const instruction = `
33208
+ if (deferredNoteText) {
33209
+ const instruction = `
32718
33210
 
32719
33211
  <instruction name="deferred_notes">${deferredNoteText}</instruction>`;
32720
- const anchoredMessageId = findLastUserMessageId(args.messages);
32721
- const outcome = markNoteNudgeDelivered(args.db, args.sessionId, instruction, anchoredMessageId);
32722
- if (anchoredMessageId && outcome.ok) {
32723
- appendReminderToUserMessageById(args.messages, anchoredMessageId, instruction);
32724
- } else if (anchoredMessageId && !outcome.ok) {
32725
- sessionLog(args.sessionId, `rust note-nudge delivery skipped wire append: ${outcome.kind}`);
33212
+ const anchoredMessageId = findLastUserMessageId(args.messages);
33213
+ const outcome = markNoteNudgeDelivered(args.db, args.sessionId, instruction, anchoredMessageId);
33214
+ if (anchoredMessageId && outcome.ok) {
33215
+ appendReminderToUserMessageById(args.messages, anchoredMessageId, instruction);
33216
+ } else if (anchoredMessageId && !outcome.ok) {
33217
+ sessionLog(args.sessionId, `rust note-nudge delivery skipped wire append: ${outcome.kind}`);
33218
+ }
33219
+ }
33220
+ const recoveryMessageIds = new Set;
33221
+ let thinkingBindingRecovery = null;
33222
+ if (modelAcceptsEmptyContent(args.resolvedProviderID)) {
33223
+ try {
33224
+ for (const id of getMergedReasoningStrippedIds(args.db, args.sessionId)) {
33225
+ if (!id.startsWith(THINKING_BINDING_RECOVERY_FROZEN_PREFIX))
33226
+ continue;
33227
+ const messageId2 = id.slice(THINKING_BINDING_RECOVERY_FROZEN_PREFIX.length);
33228
+ if (messageId2.length > 0)
33229
+ recoveryMessageIds.add(messageId2);
33230
+ }
33231
+ const flagTarget = args.thinkingBindingRecoveryEnabledForModel ? getThinkingBindingRecoveryTarget(args.db, args.sessionId) : null;
33232
+ if (flagTarget) {
33233
+ const messageId2 = flagTarget === NEWEST_REASONING_BEARING_ASSISTANT ? findNewestReasoningBearingAssistantId(args.messages) : flagTarget;
33234
+ if (messageId2 && assistantHasReasoningPart(args.messages, messageId2)) {
33235
+ const frozenId = thinkingBindingRecoveryFrozenId(messageId2);
33236
+ const persisted = recoveryMessageIds.has(messageId2) || addMergedReasoningStrippedIds(args.db, args.sessionId, [frozenId]);
33237
+ if (persisted) {
33238
+ recoveryMessageIds.add(messageId2);
33239
+ thinkingBindingRecovery = { flagTarget, messageId: messageId2 };
33240
+ } else {
33241
+ sessionLog(args.sessionId, "rust thinking binding recovery: persistence failed; leaving the bound block intact");
33242
+ }
33243
+ }
33244
+ }
33245
+ } catch (error) {
33246
+ sessionLog(args.sessionId, "rust thinking binding recovery failed:", error);
33247
+ }
33248
+ stripReasoningFromAssistantIds(args.messages, args.resolvedProviderID, recoveryMessageIds);
32726
33249
  }
33250
+ return { thinkingBindingRecovery };
32727
33251
  }
32728
33252
  function dropMarkerSummaryTag(db, sessionId, summaryMessageId) {
32729
33253
  const tagNumber = getTagNumberByMessageId(db, sessionId, `${summaryMessageId}:p0`);
@@ -32847,22 +33371,13 @@ function finalizeMessageRepresentation(messages, resolvedProviderID, options) {
32847
33371
  clearedParts = stripClearedReasoning(targetedMessages);
32848
33372
  }
32849
33373
  }
32850
- let newestAssistant = options?.trailingBlankNewestAssistant;
32851
- if (!newestAssistant) {
32852
- for (let index = messages.length - 1;index >= 0; index -= 1) {
32853
- const message = messages[index];
32854
- if (message.info.role !== "assistant")
32855
- continue;
32856
- newestAssistant = message;
32857
- break;
32858
- }
32859
- }
32860
- const mergedReasoningParts = options?.skipMergedReasoningStrip ? 0 : stripReasoningFromMergedAssistants(messages, resolvedProviderID, {
33374
+ const bindingRecoveryParts = options?.skipMergedReasoningStrip ? 0 : stripReasoningFromAssistantIds(messages, resolvedProviderID, options?.thinkingBindingRecoveryMessageIds ?? new Set);
33375
+ const mergedReasoningParts = bindingRecoveryParts + (options?.skipMergedReasoningStrip ? 0 : stripReasoningFromMergedAssistants(messages, resolvedProviderID, {
32861
33376
  mutationExemptMessage: options?.reasoningMutationExemptMessage,
32862
33377
  frozenMessageIds: options?.mergedReasoningStrippedIds
32863
- });
33378
+ }));
32864
33379
  if (!options?.skipTrailingWhitespaceStrip && modelAcceptsEmptyContent(resolvedProviderID)) {
32865
- applyFrozenTrailingBlankDecisions(messages, typeof newestAssistant?.info.id === "string" ? newestAssistant.info.id : undefined, options?.trailingBlankDecisions ?? new Map);
33380
+ applyFrozenTrailingBlankDecisions(messages, options?.trailingBlankDecisions ?? new Map);
32866
33381
  }
32867
33382
  return { clearedParts, mergedReasoningParts };
32868
33383
  }
@@ -32964,6 +33479,10 @@ async function runPostTransformPhase(args) {
32964
33479
  const shouldReadPendingOps = !compactionOff && (materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || foldExecutedThisPass || compartmentRunning);
32965
33480
  const pendingOps = shouldReadPendingOps ? getPendingOps(args.db, args.sessionId) : [];
32966
33481
  const hasPendingUserOps = pendingOps.length > 0;
33482
+ const formatPendingOpsDepth = () => {
33483
+ const depth = getPendingOpsCount(args.db, args.sessionId);
33484
+ return depth === null ? "not loaded (deferred pass)" : String(depth);
33485
+ };
32967
33486
  const shouldApplyPendingOps = !compactionOff && (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization || foldExecutedThisPass) && (!compartmentRunning || bypassCompartmentGate);
32968
33487
  const shouldRunHeuristics = !compactionOff && (!compartmentRunning || bypassCompartmentGate) && (materializationRequested || forceMaterialization || foldExecutedThisPass || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
32969
33488
  const isCacheBustingPass = shouldApplyPendingOps || shouldRunHeuristics;
@@ -32983,10 +33502,15 @@ async function runPostTransformPhase(args) {
32983
33502
  const subagentRerun = !args.fullFeatureMode && alreadyRanThisTurn && args.schedulerDecision === "execute" && !isExplicitFlush && !forceMaterialization;
32984
33503
  const reason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : forceMaterialization ? `force_materialization (${args.contextUsage.percentage.toFixed(1)}% >= ${args.forceMaterializationPercentage}%)` : foldExecutedThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into executed m[0] bust, scheduler=${args.schedulerDecision})` : subagentRerun ? `scheduler_execute_subagent_rerun (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
32985
33504
  sessionLog(args.sessionId, `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=${args.currentTurnId}`);
33505
+ } else if (args.schedulerDeferReason !== undefined && args.schedulerDeferReason !== null) {
33506
+ sessionLog(args.sessionId, `heuristics WILL NOT RUN — reason=${args.schedulerDeferReason}`);
32986
33507
  }
32987
33508
  if (alreadyRanThisTurn && args.schedulerDecision === "execute" && !materializationRequested && args.fullFeatureMode) {
32988
33509
  sessionLog(args.sessionId, `transform: skipping heuristics (already ran for turn ${args.currentTurnId})`);
32989
33510
  }
33511
+ if (!shouldApplyPendingOps && args.schedulerDeferReason !== undefined && args.schedulerDeferReason !== null) {
33512
+ sessionLog(args.sessionId, `pending ops WILL NOT APPLY — reason=${args.schedulerDeferReason} pendingOps=${formatPendingOpsDepth()} context=${args.contextUsage.percentage.toFixed(1)}%`);
33513
+ }
32990
33514
  if (compartmentRunning && hasPendingUserOps) {
32991
33515
  if (bypassCompartmentGate) {
32992
33516
  const bypassReason = forceMaterialization ? `emergency >=${args.forceMaterializationPercentage}%` : "m0 hard fold";
@@ -33021,7 +33545,7 @@ async function runPostTransformPhase(args) {
33021
33545
  try {
33022
33546
  if (shouldApplyPendingOps) {
33023
33547
  const applyReason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : foldExecutedThisPass && args.schedulerDecision !== "execute" ? `m0_hard_fold (drain folded into executed m[0] bust, scheduler=${args.schedulerDecision})` : `scheduler_execute (scheduler=${args.schedulerDecision})`;
33024
- sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
33548
+ sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${formatPendingOpsDepth()}, context=${args.contextUsage.percentage.toFixed(1)}%`);
33025
33549
  const tApply = performance.now();
33026
33550
  pendingOpsDidMutate = applyPendingOperations(args.sessionId, args.db, args.targets, args.protectedTags, undefined, pendingOps);
33027
33551
  if (pendingOpsDidMutate) {
@@ -33511,10 +34035,34 @@ async function runPostTransformPhase(args) {
33511
34035
  }
33512
34036
  }
33513
34037
  const mergedReasoningStrippedIds = new Set;
34038
+ const thinkingBindingRecoveryMessageIds = new Set;
34039
+ let thinkingBindingRecovery = null;
33514
34040
  if (canUseEmptySentinels && !compactionOff) {
33515
34041
  try {
33516
34042
  for (const id of getMergedReasoningStrippedIds(args.db, args.sessionId)) {
33517
34043
  mergedReasoningStrippedIds.add(id);
34044
+ if (id.startsWith(THINKING_BINDING_RECOVERY_FROZEN_PREFIX)) {
34045
+ const messageId2 = id.slice(THINKING_BINDING_RECOVERY_FROZEN_PREFIX.length);
34046
+ if (messageId2.length > 0)
34047
+ thinkingBindingRecoveryMessageIds.add(messageId2);
34048
+ }
34049
+ }
34050
+ const flagTarget = args.thinkingBindingRecoveryEnabledForModel ? getThinkingBindingRecoveryTarget(args.db, args.sessionId) : null;
34051
+ if (flagTarget) {
34052
+ const messageId2 = flagTarget === NEWEST_REASONING_BEARING_ASSISTANT ? findNewestReasoningBearingAssistantId(args.messages) : flagTarget;
34053
+ if (messageId2 && assistantHasReasoningPart(args.messages, messageId2)) {
34054
+ const frozenId = thinkingBindingRecoveryFrozenId(messageId2);
34055
+ const persisted = mergedReasoningStrippedIds.has(frozenId) || addMergedReasoningStrippedIds(args.db, args.sessionId, [frozenId]);
34056
+ if (persisted) {
34057
+ mergedReasoningStrippedIds.add(frozenId);
34058
+ thinkingBindingRecoveryMessageIds.add(messageId2);
34059
+ thinkingBindingRecovery = { flagTarget, messageId: messageId2 };
34060
+ bustedThisPass = true;
34061
+ } else {
34062
+ args.passOutcome?.record("thinking-binding-recovery-persistence-failure");
34063
+ sessionLog(args.sessionId, "thinking binding recovery: persistence failed; leaving the bound block intact");
34064
+ }
34065
+ }
33518
34066
  }
33519
34067
  if (isCacheBustingPass) {
33520
34068
  const candidates = findMergedReasoningStripCandidateIds(args.messages, args.resolvedProviderID, { mutationExemptMessage: reasoningMutationExemptMessage });
@@ -33578,17 +34126,6 @@ async function runPostTransformPhase(args) {
33578
34126
  }
33579
34127
  }
33580
34128
  }
33581
- const tFinalRepresentation = performance.now();
33582
- const finalRepresentation = finalizeMessageRepresentation(args.messages, args.resolvedProviderID, {
33583
- prependedMessageCount,
33584
- reasoningMutatedMessages,
33585
- reasoningMutationExemptMessage,
33586
- trailingBlankNewestAssistant,
33587
- mergedReasoningStrippedIds,
33588
- trailingBlankDecisions,
33589
- skipMergedReasoningStrip: compactionOff,
33590
- skipTrailingWhitespaceStrip: compactionOff
33591
- });
33592
34129
  if (canUseEmptySentinels && !compactionOff) {
33593
34130
  const detectedCandidates = findTrailingBlankDecisionCandidates(args.messages, trailingBlankDecisions, {
33594
34131
  refreshMessageId: newestAssistantId,
@@ -33602,20 +34139,16 @@ async function runPostTransformPhase(args) {
33602
34139
  });
33603
34140
  if (persisted) {
33604
34141
  const committed = getTrailingBlankDecisions(args.db, args.sessionId);
33605
- const newlyFrozen = new Map;
33606
34142
  for (const [id] of candidates) {
33607
34143
  const decision = committed.get(id);
33608
- if (!decision)
33609
- continue;
33610
- trailingBlankDecisions.set(id, decision);
33611
- newlyFrozen.set(id, decision);
34144
+ if (decision)
34145
+ trailingBlankDecisions.set(id, decision);
33612
34146
  }
33613
- applyFrozenTrailingBlankDecisions(args.messages, newestAssistantId, newlyFrozen);
33614
34147
  if (isCacheBustingPass)
33615
34148
  bustedThisPass = true;
33616
34149
  } else {
33617
34150
  args.passOutcome?.record("trailing-blank-decision-persistence-failure");
33618
- sessionLog(args.sessionId, "trailing blank decision: persistence failed; leaving newly observed assistants intact");
34151
+ sessionLog(args.sessionId, "trailing blank decision: persistence failed; serving the frozen decisions");
33619
34152
  }
33620
34153
  } catch (error) {
33621
34154
  args.passOutcome?.record("trailing-blank-decision-exception");
@@ -33623,6 +34156,17 @@ async function runPostTransformPhase(args) {
33623
34156
  }
33624
34157
  }
33625
34158
  }
34159
+ const tFinalRepresentation = performance.now();
34160
+ const finalRepresentation = finalizeMessageRepresentation(args.messages, args.resolvedProviderID, {
34161
+ prependedMessageCount,
34162
+ reasoningMutatedMessages,
34163
+ reasoningMutationExemptMessage,
34164
+ mergedReasoningStrippedIds,
34165
+ thinkingBindingRecoveryMessageIds,
34166
+ trailingBlankDecisions,
34167
+ skipMergedReasoningStrip: compactionOff,
34168
+ skipTrailingWhitespaceStrip: compactionOff
34169
+ });
33626
34170
  sessionLog(args.sessionId, `final representation: clearedParts=${finalRepresentation.clearedParts} mergedReasoningParts=${finalRepresentation.mergedReasoningParts}`);
33627
34171
  logTransformTiming(args.sessionId, "finalizeMessageRepresentation", tFinalRepresentation, `clearedParts=${finalRepresentation.clearedParts} mergedReasoningParts=${finalRepresentation.mergedReasoningParts}`);
33628
34172
  let assertedBaseline;
@@ -33717,7 +34261,8 @@ async function runPostTransformPhase(args) {
33717
34261
  emergencyReclaimedTokens,
33718
34262
  droppedCount,
33719
34263
  emergency,
33720
- bustedThisPass
34264
+ bustedThisPass,
34265
+ thinkingBindingRecovery
33721
34266
  };
33722
34267
  }
33723
34268
  function checkM0MutationDriftAndSignal(args) {
@@ -34001,7 +34546,9 @@ function formatRustPassLog(args) {
34001
34546
  const measured = timings.prefixGuard + timings.ordinalResolve + timings.stateSync + timings.clone + timings.wireBuild + timings.transport + timings.apply + timings.lkgSnapshot;
34002
34547
  const unattributed = Math.max(0, args.elapsedMs - measured);
34003
34548
  const rowVersion = Number.isSafeInteger(args.rowVersion) ? args.rowVersion : 0;
34004
- return `rust pass: decision=${args.decision} reason=${args.reason} served_from=${args.servedFrom} in=${args.inputCount} out=${args.outputCount} applied=${args.applied} row_version=${rowVersion} elapsed=${args.elapsedMs.toFixed(1)} ms module=${args.moduleElapsedMs.toFixed(1)} ms stages=prefix_guard:${timings.prefixGuard.toFixed(1)} ordinal_resolve:${timings.ordinalResolve.toFixed(1)} state_sync:${timings.stateSync.toFixed(1)} clone:${timings.clone.toFixed(1)} wire_build:${timings.wireBuild.toFixed(1)} wire_messages:${timings.wireMessages} transport:${timings.transport.toFixed(1)} transport_pages:${timings.transportPages} transport_bytes:${timings.transportBytes} apply:${timings.apply.toFixed(1)} lkg_snapshot:${timings.lkgSnapshot.toFixed(1)} mirror_pull:${timings.mirrorPull.toFixed(1)} compartment_mirror:${timings.compartmentMirror.toFixed(1)} other:${unattributed.toFixed(1)}`;
34549
+ const schedulerFields = args.schedulerDecision ? ` scheduler=${args.schedulerDecision}${args.schedulerDeferReason ? ` defer_reason=${args.schedulerDeferReason}` : ""}` : "";
34550
+ const historianFields = args.historianCanonicalCause ? ` historian_no_fire=${args.historianNoFire ?? "unknown"} canonical_cause=${args.historianCanonicalCause}` : "";
34551
+ return `rust pass: decision=${args.decision} reason=${args.reason}${schedulerFields}${historianFields} served_from=${args.servedFrom} in=${args.inputCount} out=${args.outputCount} applied=${args.applied} row_version=${rowVersion} elapsed=${args.elapsedMs.toFixed(1)} ms module=${args.moduleElapsedMs.toFixed(1)} ms stages=prefix_guard:${timings.prefixGuard.toFixed(1)} ordinal_resolve:${timings.ordinalResolve.toFixed(1)} state_sync:${timings.stateSync.toFixed(1)} clone:${timings.clone.toFixed(1)} wire_build:${timings.wireBuild.toFixed(1)} wire_messages:${timings.wireMessages} transport:${timings.transport.toFixed(1)} transport_pages:${timings.transportPages} transport_bytes:${timings.transportBytes} apply:${timings.apply.toFixed(1)} lkg_snapshot:${timings.lkgSnapshot.toFixed(1)} mirror_pull:${timings.mirrorPull.toFixed(1)} compartment_mirror:${timings.compartmentMirror.toFixed(1)} other:${unattributed.toFixed(1)}`;
34005
34552
  }
34006
34553
  function isSyntheticUserMessage(message) {
34007
34554
  if (!message || messageInfo2(message).role !== "user" || !Array.isArray(message.parts)) {
@@ -34549,6 +35096,7 @@ function createRustModeTransform(deps, options) {
34549
35096
  const states = new Map;
34550
35097
  const wireCaches = new Map;
34551
35098
  const scheduleLkgCapture = options.scheduleLkgCapture ?? ((capture) => setImmediate(capture));
35099
+ const installNativeMessages = options.installNativeMessagesForTests ?? replaceMessagesInPlace;
34552
35100
  const rawFallbackEstimator = options.rawFallbackEstimatorForTests ?? estimateFinalWireInputTokens;
34553
35101
  const timeoutMs = Math.max(1, options.moduleTimeoutMs ?? RUST_SEND_TIMEOUT_MS);
34554
35102
  const logStage = (sessionId, stage, startedAt, timings, extra) => {
@@ -34636,6 +35184,12 @@ function createRustModeTransform(deps, options) {
34636
35184
  return false;
34637
35185
  }
34638
35186
  const replayModel = modelFromMessages(currentMessages) ?? findLastAssistantModelFromOpenCodeDb(sessionId);
35187
+ replayRustModeBindingMismatchStrips({
35188
+ db: deps.db,
35189
+ sessionId,
35190
+ messages: replay.messages,
35191
+ resolvedProviderID: replayModel?.providerID
35192
+ });
34639
35193
  const trustedReplayLimit = replayModel ? resolveTrustedContextLimit(replayModel.providerID, replayModel.modelID, {
34640
35194
  db: deps.db,
34641
35195
  sessionID: sessionId
@@ -34668,23 +35222,21 @@ function createRustModeTransform(deps, options) {
34668
35222
  sessionLog(sessionId, "lkg_replay_served");
34669
35223
  return true;
34670
35224
  };
34671
- const prepareRustCapture = (state, sessionId, input, inputSnapshots, nativeMessages, responseRowVersion) => {
35225
+ const prepareRustCapture = (state, sessionId, inputIds, inputKeys, inputSnapshots, nativeMessages, responseRowVersion) => {
34672
35226
  state.lkgCaptureSequence += 1;
34673
- const inputIds = input.map((message) => message.info.id);
34674
35227
  if (inputIds.some((id) => typeof id !== "string") || new Set(inputIds).size !== inputIds.length || inputIds.length === 0 || inputSnapshots.length !== inputIds.length) {
34675
35228
  return null;
34676
35229
  }
34677
35230
  const jsonPrefix = JSON.stringify(nativeMessages);
34678
35231
  if (typeof jsonPrefix !== "string")
34679
35232
  return null;
34680
- const keys = resolveLkgModelKeys(input);
34681
35233
  return {
34682
35234
  sessionId,
34683
35235
  inputIds,
34684
35236
  inputSnapshots,
34685
35237
  jsonPrefix,
34686
- modelKey: keys.modelKey,
34687
- providerKey: keys.providerKey,
35238
+ modelKey: inputKeys.modelKey,
35239
+ providerKey: inputKeys.providerKey,
34688
35240
  capturedAt: Date.now(),
34689
35241
  rowVersion: responseRowVersion,
34690
35242
  captureSequence: state.lkgCaptureSequence
@@ -34729,6 +35281,8 @@ function createRustModeTransform(deps, options) {
34729
35281
  const passStartedAt = performance.now();
34730
35282
  const passObservedAtMs = Date.now();
34731
35283
  const state = ensureState(states, sessionId);
35284
+ const trailingBlankSourceDecisions = snapshotTrailingBlankSourceDecisions(messages);
35285
+ const trailingBlankNewestAssistantId = [...messages].reverse().find((message) => message.info.role === "assistant")?.info.id;
34732
35286
  const timings = emptyRustPassTimings();
34733
35287
  state.passCount += 1;
34734
35288
  const syntheticTurn = observeSyntheticTurn(state, messages);
@@ -34741,6 +35295,10 @@ function createRustModeTransform(deps, options) {
34741
35295
  let requestInputTokens = 0;
34742
35296
  let decision = "error";
34743
35297
  let materializeReason = "none";
35298
+ let schedulerDecision;
35299
+ let schedulerDeferReason;
35300
+ let historianNoFire;
35301
+ let historianCanonicalCause;
34744
35302
  let servedFrom = "none";
34745
35303
  let moduleElapsedMs = 0;
34746
35304
  let rowVersion = 0;
@@ -34819,6 +35377,10 @@ function createRustModeTransform(deps, options) {
34819
35377
  sessionLog(sessionId, formatRustPassLog({
34820
35378
  decision,
34821
35379
  reason: materializeReason,
35380
+ schedulerDecision,
35381
+ schedulerDeferReason,
35382
+ historianNoFire,
35383
+ historianCanonicalCause,
34822
35384
  servedFrom,
34823
35385
  inputCount,
34824
35386
  outputCount: output.messages.length,
@@ -34841,6 +35403,11 @@ function createRustModeTransform(deps, options) {
34841
35403
  const captureResponseTelemetry = (response) => {
34842
35404
  decision = typeof response.decision === "string" ? response.decision : typeof response.action === "string" ? response.action : typeof response.status === "string" ? response.status : "unknown";
34843
35405
  servedFrom = typeof response.served_from === "string" ? response.served_from : "unknown";
35406
+ schedulerDecision = typeof response.scheduler_decision === "string" ? response.scheduler_decision : undefined;
35407
+ schedulerDeferReason = typeof response.scheduler_defer_reason === "string" ? response.scheduler_defer_reason : undefined;
35408
+ const historian = isRecord4(response.historian) ? response.historian : undefined;
35409
+ historianNoFire = typeof historian?.no_fire === "string" ? historian.no_fire : undefined;
35410
+ historianCanonicalCause = typeof historian?.canonical_cause === "string" ? historian.canonical_cause : undefined;
34844
35411
  materializeReason = typeof response.materialize_reason === "string" && response.materialize_reason.length > 0 ? response.materialize_reason : "none";
34845
35412
  const timings2 = isRecord4(response.timings) ? response.timings : undefined;
34846
35413
  const applyOnceTotal = timings2?.total;
@@ -35363,6 +35930,7 @@ function createRustModeTransform(deps, options) {
35363
35930
  const decisionUpper = decision.toUpperCase();
35364
35931
  const cacheBustingPass = response.cache_bust === true || decisionUpper === "HARD" || decisionUpper === "MIGRATE_HARD" || decisionUpper === "EXECUTE" || decisionUpper === "SOFT" || !explicitDecision;
35365
35932
  let appliedMessages;
35933
+ let thinkingBindingRecovery = null;
35366
35934
  const applyStartedAt = performance.now();
35367
35935
  try {
35368
35936
  const moduleMessages = applyNativeMessagesVerbatim({ messages: [] }, response, previousWireCache?.nativeOutput ? {
@@ -35390,16 +35958,20 @@ function createRustModeTransform(deps, options) {
35390
35958
  }
35391
35959
  pendingWireCache.nativeOutput = appliedMessages;
35392
35960
  if (!replayedFrozenRepresentation) {
35393
- runRustModePostprocess({
35961
+ thinkingBindingRecovery = runRustModePostprocess({
35394
35962
  db: deps.db,
35395
35963
  sessionId,
35396
35964
  messages: appliedMessages,
35397
35965
  projectPath: memoryProjectPath,
35398
35966
  fullFeatureMode: !sessionMeta.isSubagent,
35399
35967
  compactionOff: deps.compactionOff,
35968
+ resolvedProviderID: model?.providerID,
35969
+ thinkingBindingRecoveryEnabledForModel: isFable51ThinkingBindingModel(model?.providerID, model?.modelID),
35970
+ trailingBlankSourceDecisions,
35971
+ trailingBlankNewestAssistantId: typeof trailingBlankNewestAssistantId === "string" ? trailingBlankNewestAssistantId : undefined,
35400
35972
  tagger: deps.tagger,
35401
35973
  ctxReduceAvailability: reduceAvailability
35402
- });
35974
+ }).thinkingBindingRecovery;
35403
35975
  }
35404
35976
  const boundaryId = response.boundary_id;
35405
35977
  if (typeof boundaryId === "string" && boundaryId.length > 0) {
@@ -35423,11 +35995,20 @@ function createRustModeTransform(deps, options) {
35423
35995
  });
35424
35996
  }
35425
35997
  logStage(sessionId, "apply", applyStartedAt, timings);
35998
+ const lkgInputIds = messages.map((message) => message.info.id);
35999
+ const lkgInputKeys = resolveLkgModelKeys(messages);
36000
+ const applyReplaceStartedAt = performance.now();
36001
+ installNativeMessages(output, appliedMessages);
36002
+ logStage(sessionId, "apply", applyReplaceStartedAt, timings);
36003
+ if (thinkingBindingRecovery) {
36004
+ const cleared = clearThinkingBindingRecoveryIf(deps.db, sessionId, thinkingBindingRecovery.flagTarget);
36005
+ sessionLog(sessionId, `rust thinking binding recovery: stripped bound reasoning from assistant ${thinkingBindingRecovery.messageId}; flag=${cleared ? "cleared" : "rearmed"}`);
36006
+ }
35426
36007
  const lkgSnapshotStartedAt = performance.now();
35427
36008
  if (cacheBustingPass) {
35428
36009
  dropSlot(sessionId, "lkg_cache_bust_pending_capture");
35429
36010
  }
35430
- const capturePlan = prepareRustCapture(state, sessionId, messages, pendingWireCache.rawContentSnapshots, appliedMessages, rowVersion);
36011
+ const capturePlan = prepareRustCapture(state, sessionId, lkgInputIds, lkgInputKeys, pendingWireCache.rawContentSnapshots, output.messages, rowVersion);
35431
36012
  let captureMode = "async";
35432
36013
  const captureFailed = (mode, error) => {
35433
36014
  if (states.get(sessionId) !== state || capturePlan && capturePlan.captureSequence !== state.lkgCaptureSequence) {
@@ -35464,9 +36045,6 @@ function createRustModeTransform(deps, options) {
35464
36045
  }
35465
36046
  }
35466
36047
  logStage(sessionId, "lkgSnapshot", lkgSnapshotStartedAt, timings, `mode=${captureMode} row_version=${rowVersion}`);
35467
- const applyReplaceStartedAt = performance.now();
35468
- replaceMessagesInPlace(output, appliedMessages);
35469
- logStage(sessionId, "apply", applyReplaceStartedAt, timings);
35470
36048
  } catch (error) {
35471
36049
  logStage(sessionId, "apply", applyStartedAt, timings, "failed=true");
35472
36050
  try {
@@ -35612,17 +36190,24 @@ function createRustModeTransform(deps, options) {
35612
36190
  };
35613
36191
  return {
35614
36192
  run,
35615
- clearSession(sessionId) {
36193
+ async clearSession(sessionId) {
35616
36194
  const projectRoot = states.get(sessionId)?.memoryAuthorityRoot ?? options.projectRoot ?? null;
35617
- dropSlot(sessionId, "session-deleted");
35618
- states.delete(sessionId);
35619
- wireCaches.delete(sessionId);
35620
- clearCompartmentMirrorCursor(sessionId);
35621
- if (projectRoot && options.moduleClient.deleteSession) {
35622
- options.moduleClient.deleteSession(sessionId, projectRoot).catch((error) => {
35623
- sessionLog(sessionId, "rust module session deletion failed:", error);
35624
- }).finally(() => options.moduleClient.closeSession?.(sessionId));
35625
- } else {
36195
+ const clearLocalState = () => {
36196
+ dropSlot(sessionId, "session-deleted");
36197
+ states.delete(sessionId);
36198
+ wireCaches.delete(sessionId);
36199
+ clearCompartmentMirrorCursor(sessionId);
36200
+ };
36201
+ clearLocalState();
36202
+ try {
36203
+ if (projectRoot && options.moduleClient.deleteSession) {
36204
+ await options.moduleClient.deleteSession(sessionId, projectRoot);
36205
+ }
36206
+ } catch (error) {
36207
+ sessionLog(sessionId, "rust module session deletion failed:", error);
36208
+ throw error;
36209
+ } finally {
36210
+ clearLocalState();
35626
36211
  options.moduleClient.closeSession?.(sessionId);
35627
36212
  }
35628
36213
  },
@@ -36295,7 +36880,11 @@ function createTransform(deps) {
36295
36880
  sessionId,
36296
36881
  effectiveExecuteThresholdPercentage
36297
36882
  });
36298
- const { midTurnAdjustedSchedulerDecision, sideEffect } = applyMidTurnDeferral({
36883
+ const {
36884
+ midTurnAdjustedSchedulerDecision,
36885
+ sideEffect,
36886
+ deferReason: schedulerDeferReason
36887
+ } = applyMidTurnDeferral({
36299
36888
  base: schedulerDecisionEarly,
36300
36889
  bypassReason,
36301
36890
  midTurn
@@ -36664,6 +37253,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
36664
37253
  contextUsage,
36665
37254
  usableWindow: resolvedContextLimit ?? 0,
36666
37255
  schedulerDecision,
37256
+ schedulerDeferReason,
36667
37257
  fullFeatureMode,
36668
37258
  compactionOff,
36669
37259
  canRunCompartments,
@@ -36696,6 +37286,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
36696
37286
  cavemanTextCompression: !reducedMode ? deps.cavemanTextCompression : undefined,
36697
37287
  smartDrops: deps.smartDrops === true,
36698
37288
  resolvedProviderID,
37289
+ thinkingBindingRecoveryEnabledForModel: isFable51ThinkingBindingModel(modelForBudget?.providerID, modelForBudget?.modelID),
36699
37290
  trailingBlankSourceDecisions,
36700
37291
  passOutcome,
36701
37292
  historyRefreshSessions: deps.historyRefreshSessions,
@@ -36882,13 +37473,18 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
36882
37473
  const elapsed = (performance.now() - startTime).toFixed(1);
36883
37474
  sessionLog(sessionId, `transform completed in ${elapsed}ms (${messages.length} messages, ${targets.size} targets, watermark: ${watermark})`);
36884
37475
  deps.maybeAutoEmbedSession?.(sessionId);
37476
+ const bindingRecovery = postTransformResult.thinkingBindingRecovery;
37477
+ if (bindingRecovery) {
37478
+ const cleared = clearThinkingBindingRecoveryIf(db, sessionId, bindingRecovery.flagTarget);
37479
+ sessionLog(sessionId, `thinking binding recovery: stripped bound reasoning from assistant ${bindingRecovery.messageId}; flag=${cleared ? "cleared" : "rearmed"}`);
37480
+ }
36885
37481
  };
36886
37482
  return Object.assign(transform, {
36887
37483
  invalidateRustWireState(sessionId) {
36888
37484
  rustModeTransform?.invalidateWireState(sessionId);
36889
37485
  },
36890
- clearRustSession(sessionId) {
36891
- rustModeTransform?.clearSession(sessionId);
37486
+ async clearRustSession(sessionId) {
37487
+ await rustModeTransform?.clearSession(sessionId);
36892
37488
  }
36893
37489
  });
36894
37490
  }
@@ -37006,6 +37602,16 @@ function createEventHandler2(deps) {
37006
37602
  return;
37007
37603
  }
37008
37604
  try {
37605
+ const bindingMismatch = detectThinkingBindingMismatch(errInfo.error);
37606
+ if (bindingMismatch.isBindingMismatch) {
37607
+ const model = findLastAssistantModelFromOpenCodeDb(errInfo.sessionID);
37608
+ if (deps.thinkingBindingRecoveryEnabled !== false && !deps.compactionOff && isFable51ThinkingBindingModel(model?.providerID, model?.modelID)) {
37609
+ armThinkingBindingRecovery(deps.db, errInfo.sessionID, bindingMismatch.messageId);
37610
+ dropSlot(errInfo.sessionID, "thinking-binding-recovery-arm");
37611
+ deps.onSessionCacheInvalidated?.(errInfo.sessionID);
37612
+ }
37613
+ return;
37614
+ }
37009
37615
  const detection = detectOverflow(errInfo.error);
37010
37616
  if (!detection.isOverflow) {
37011
37617
  return;
@@ -37069,6 +37675,18 @@ function createEventHandler2(deps) {
37069
37675
  });
37070
37676
  }
37071
37677
  let messageHadOverflowError = false;
37678
+ if (info.error !== undefined && info.error !== null) {
37679
+ const bindingMismatch = detectThinkingBindingMismatch(info.error);
37680
+ if (bindingMismatch.isBindingMismatch && deps.thinkingBindingRecoveryEnabled !== false && !deps.compactionOff && isFable51ThinkingBindingModel(info.providerID, info.modelID)) {
37681
+ try {
37682
+ armThinkingBindingRecovery(deps.db, info.sessionID, bindingMismatch.messageId);
37683
+ dropSlot(info.sessionID, "thinking-binding-recovery-arm");
37684
+ deps.onSessionCacheInvalidated?.(info.sessionID);
37685
+ } catch (error) {
37686
+ sessionLog(info.sessionID, "event message.updated thinking binding recovery persistence failed:", error);
37687
+ }
37688
+ }
37689
+ }
37072
37690
  if (info.error !== undefined && info.error !== null) {
37073
37691
  const detection = detectOverflow(info.error);
37074
37692
  if (detection.isOverflow) {
@@ -37279,15 +37897,17 @@ function createEventHandler2(deps) {
37279
37897
  }
37280
37898
  dropSlot(sessionId, "session.deleted");
37281
37899
  try {
37282
- markSessionCleanupPending(deps.db, sessionId);
37283
- removeCompactionMarkerForSession(deps.db, sessionId);
37284
- clearSession(deps.db, sessionId);
37900
+ const rustCleanupPending = markSessionCleanupPending(deps.db, sessionId, deps.rustSessionCleanup === true);
37901
+ await deps.onSessionDeleted?.(sessionId);
37902
+ if (!rustCleanupPending || deps.rustSessionCleanup === true) {
37903
+ removeCompactionMarkerForSession(deps.db, sessionId);
37904
+ clearSession(deps.db, sessionId, deps.rustSessionCleanup === true);
37905
+ }
37285
37906
  } catch (error) {
37286
37907
  sessionLog(sessionId, "event session.deleted persistence failed:", error);
37287
37908
  }
37288
37909
  resetDegradedCacheCount(sessionId);
37289
37910
  deps.onSessionCacheInvalidated?.(sessionId);
37290
- deps.onSessionDeleted?.(sessionId);
37291
37911
  deps.contextUsageMap.delete(sessionId);
37292
37912
  deps.tagger.cleanup(sessionId);
37293
37913
  clearTransformDecisionSession(sessionId);
@@ -37298,6 +37918,40 @@ function createEventHandler2(deps) {
37298
37918
  };
37299
37919
  }
37300
37920
 
37921
+ // src/hooks/magic-context/format-embed-failure.ts
37922
+ function formatEmbedFailureSummary(embedded, remaining, failure) {
37923
+ const compartment = `compartment${embedded === 1 ? "" : "s"}`;
37924
+ if (!failure) {
37925
+ return `Embedded ${embedded} ${compartment}; ${remaining} could not be embedded (the provider returned no result). Run /ctx-embed start again to retry them.`;
37926
+ }
37927
+ const retry = " Run /ctx-embed start again to retry them.";
37928
+ switch (failure.class) {
37929
+ case "substitution_rejected":
37930
+ return `Embedded ${embedded} ${compartment}; ${remaining} rejected: ${failure.reason}. Fix: set embedding.model to the served spelling.`;
37931
+ case "http_error": {
37932
+ const fix = failure.reason.startsWith("HTTP 401") || failure.reason.startsWith("HTTP 403") ? " Fix: check embedding.api_key." : failure.reason.startsWith("HTTP 402") ? " Fix: check provider quota or billing." : failure.reason.startsWith("HTTP 404") ? " Fix: check embedding.endpoint and embedding.model." : failure.retryable ? retry : " Fix: check embedding.endpoint, embedding.model, and credentials.";
37933
+ return `Embedded ${embedded} ${compartment}; ${remaining} failed: ${failure.reason}.${fix}`;
37934
+ }
37935
+ case "invalid_envelope":
37936
+ return `Embedded ${embedded} ${compartment}; ${remaining} failed: ${failure.reason}. Fix: configure an OpenAI-compatible embedding endpoint that returns data[].embedding.`;
37937
+ case "certification_refusal":
37938
+ return `Embedded ${embedded} ${compartment}; ${remaining} rejected: ${failure.reason}. Fix: recertify SYNAPSE or configure embedding.fallback_provider, then run /ctx-embed start again.`;
37939
+ case "credential_required":
37940
+ return `Embedded ${embedded} ${compartment}; ${remaining} rejected: ${failure.reason}. Fix: reauthenticate SYNAPSE, then run /ctx-embed start again.`;
37941
+ case "local_binding_missing":
37942
+ return `Embedded ${embedded} ${compartment}; ${remaining} failed: ${failure.reason}. Fix: run \`npx @cortexkit/magic-context@latest doctor\` to verify the WASM fallback. On Intel macOS, onnxruntime-node@1.23.0 may be pinned manually for optional native speed.`;
37943
+ case "local_fs_unavailable":
37944
+ return `Embedded ${embedded} ${compartment}; ${remaining} failed: ${failure.reason}. Fix: update or reinstall Magic Context so its Node WASM bundle can persist the model, then run /ctx-embed start again.`;
37945
+ case "local_download_failure":
37946
+ return `Embedded ${embedded} ${compartment}; ${remaining} failed: ${failure.reason}. Fix: check network/proxy access to the model host.${retry}`;
37947
+ case "local_runtime_error":
37948
+ return `Embedded ${embedded} ${compartment}; ${remaining} failed: ${failure.reason}. Fix: run \`npx @cortexkit/magic-context@latest doctor\` for local runtime diagnostics.`;
37949
+ case "empty_result":
37950
+ case "transport_error":
37951
+ return `Embedded ${embedded} ${compartment}; ${remaining} failed: ${failure.reason}.${failure.retryable ? retry : ""}`;
37952
+ }
37953
+ }
37954
+
37301
37955
  // src/hooks/magic-context/text-complete.ts
37302
37956
  function createTextCompleteHandler() {
37303
37957
  return async (_input, output) => {
@@ -38221,6 +38875,7 @@ function createMagicContextHook(deps) {
38221
38875
  const commitSeenLastPass = new Map;
38222
38876
  const variantBySession = deps.liveSessionState?.variantBySession ?? new Map;
38223
38877
  const liveModelBySession = deps.liveSessionState?.liveModelBySession ?? new Map;
38878
+ const latestAssistantMessageIdBySession = deps.liveSessionState?.latestAssistantMessageIdBySession ?? new Map;
38224
38879
  const agentBySession = deps.liveSessionState?.agentBySession ?? new Map;
38225
38880
  const sessionDirectoryBySession = deps.liveSessionState?.sessionDirectoryBySession ?? new Map;
38226
38881
  const internalChildSessions = deps.liveSessionState?.internalChildSessions ?? new Set;
@@ -38257,6 +38912,7 @@ function createMagicContextHook(deps) {
38257
38912
  db,
38258
38913
  liveSessionState: {
38259
38914
  liveModelBySession,
38915
+ latestAssistantMessageIdBySession,
38260
38916
  channel1StateBySession,
38261
38917
  variantBySession,
38262
38918
  agentBySession,
@@ -38372,7 +39028,7 @@ function createMagicContextHook(deps) {
38372
39028
  return terminal("skipped", msg);
38373
39029
  }
38374
39030
  case "stalled":
38375
- return terminal("skipped", `Embedded ${outcome.embedded} compartments; ${outcome.remaining} could not be embedded (the provider returned no result). Run /ctx-embed start again to retry them.`);
39031
+ return terminal("skipped", formatEmbedFailureSummary(outcome.embedded, outcome.remaining, outcome.failure));
38376
39032
  default:
38377
39033
  return terminal("done", `Embedded ${outcome.embedded} compartment${outcome.embedded === 1 ? "" : "s"} of history for semantic search${runFailed > 0 ? ` (${runFailed} failed)` : ""}.`);
38378
39034
  }
@@ -38415,7 +39071,7 @@ function createMagicContextHook(deps) {
38415
39071
  autoEmbedAttemptedBySession.add(sessionId);
38416
39072
  const directory = sessionDirectoryBySession.get(sessionId) ?? deps.directory;
38417
39073
  (async () => {
38418
- let completedDrainWithWork = false;
39074
+ let drainReachedTerminal = false;
38419
39075
  try {
38420
39076
  await new Promise((resolve3) => setTimeout(resolve3, 0));
38421
39077
  await ensureProjectRegisteredFromOpenCodeDirectory(directory, db);
@@ -38429,25 +39085,19 @@ function createMagicContextHook(deps) {
38429
39085
  const remaining = coverage.session.total - coverage.session.embedded;
38430
39086
  if (remaining <= 0)
38431
39087
  return;
38432
- const notifyParams = getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession);
38433
- if (!isTuiConnected(sessionId)) {
38434
- const startMsg = `Embedding ${remaining} compartment${remaining === 1 ? "" : "s"} of history in the background…`;
38435
- await sendIgnoredMessage(deps.client, sessionId, startMsg, {
38436
- ...notifyParams
38437
- });
38438
- }
38439
- const summary = await executeEmbedHistory(sessionId);
39088
+ const embeddedBefore = coverage.session.embedded;
39089
+ await executeEmbedHistory(sessionId, { silent: true });
39090
+ drainReachedTerminal = true;
38440
39091
  const completedCoverage = getEmbeddingCoverageStatus(db, sessionProjectIdentity, sessionId);
38441
- completedDrainWithWork = completedCoverage.session.total - completedCoverage.session.embedded <= 0;
38442
- if (!isTuiConnected(sessionId)) {
38443
- await sendIgnoredMessage(deps.client, sessionId, summary, {
38444
- ...notifyParams
38445
- });
39092
+ const embeddedNow = completedCoverage.session.embedded - embeddedBefore;
39093
+ if (embeddedNow > 0 && !isTuiConnected(sessionId)) {
39094
+ const notifyParams = getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession);
39095
+ await sendIgnoredMessage(deps.client, sessionId, `Embedded ${embeddedNow} compartment${embeddedNow === 1 ? "" : "s"} of history for semantic search.`, { ...notifyParams });
38446
39096
  }
38447
39097
  } catch (error) {
38448
39098
  log("[magic-context] auto-embed drain failed:", error);
38449
39099
  } finally {
38450
- if (!completedDrainWithWork)
39100
+ if (!drainReachedTerminal)
38451
39101
  autoEmbedAttemptedBySession.delete(sessionId);
38452
39102
  }
38453
39103
  })();
@@ -38777,6 +39427,7 @@ function createMagicContextHook(deps) {
38777
39427
  compactionHandler: deps.compactionHandler,
38778
39428
  config: deps.config,
38779
39429
  compactionOff,
39430
+ thinkingBindingRecoveryEnabled: deps.config.transform_mode !== "rust",
38780
39431
  tagger: deps.tagger,
38781
39432
  db,
38782
39433
  client: deps.client,
@@ -38792,23 +39443,27 @@ function createMagicContextHook(deps) {
38792
39443
  onRustWireInvalidated: (sessionId) => {
38793
39444
  transform.invalidateRustWireState(sessionId);
38794
39445
  },
38795
- onSessionDeleted: (sessionId) => {
39446
+ rustSessionCleanup: rustModeModuleClient !== undefined,
39447
+ onSessionDeleted: async (sessionId) => {
38796
39448
  dropSlot(sessionId, "session-deleted");
38797
- transform.clearRustSession(sessionId);
38798
- systemPromptHash.clearSession(sessionId);
38799
- lastHeuristicsTurnId.delete(sessionId);
38800
- clearToolPermissionDenied(sessionId);
38801
- commitSeenLastPass.delete(sessionId);
38802
- variantBySession.delete(sessionId);
38803
- liveModelBySession.delete(sessionId);
38804
- agentBySession.delete(sessionId);
38805
- sessionDirectoryBySession.delete(sessionId);
38806
- recompProgressBySession.delete(sessionId);
38807
- internalChildSessions.delete(sessionId);
38808
- rustMemorySyncRequestedSessions.delete(sessionId);
38809
- channel1StateBySession.delete(sessionId);
38810
- channel2DirectiveTextBySession.delete(sessionId);
38811
- clearEmbedSessionState(sessionId);
39449
+ try {
39450
+ await transform.clearRustSession(sessionId);
39451
+ } finally {
39452
+ systemPromptHash.clearSession(sessionId);
39453
+ lastHeuristicsTurnId.delete(sessionId);
39454
+ clearToolPermissionDenied(sessionId);
39455
+ commitSeenLastPass.delete(sessionId);
39456
+ variantBySession.delete(sessionId);
39457
+ liveModelBySession.delete(sessionId);
39458
+ agentBySession.delete(sessionId);
39459
+ sessionDirectoryBySession.delete(sessionId);
39460
+ recompProgressBySession.delete(sessionId);
39461
+ internalChildSessions.delete(sessionId);
39462
+ rustMemorySyncRequestedSessions.delete(sessionId);
39463
+ channel1StateBySession.delete(sessionId);
39464
+ channel2DirectiveTextBySession.delete(sessionId);
39465
+ clearEmbedSessionState(sessionId);
39466
+ }
38812
39467
  }
38813
39468
  });
38814
39469
  const runDreamQueueInBackground = () => {
@@ -38976,6 +39631,7 @@ function createMagicContextHook(deps) {
38976
39631
  contextUsageMap,
38977
39632
  db,
38978
39633
  liveModelBySession,
39634
+ latestAssistantMessageIdBySession,
38979
39635
  variantBySession,
38980
39636
  agentBySession,
38981
39637
  sessionDirectoryBySession,
@@ -39875,6 +40531,48 @@ function projectIdentityForStoredPath(rawProjectPath) {
39875
40531
  function memoryBelongsToProject(memory, projectPath) {
39876
40532
  return storedPathBelongsToIdentity(memory.projectPath, projectPath);
39877
40533
  }
40534
+ function preflightCurateMutation(args) {
40535
+ const { params } = args;
40536
+ if (params.action !== "archive" && params.action !== "update") {
40537
+ return { skip: null, successor: null };
40538
+ }
40539
+ const ids = params.ids;
40540
+ const content = params.content?.trim();
40541
+ if (!ids || ids.length === 0 || !ids.every(Number.isInteger) || params.action === "update" && (ids.length !== 1 || !content)) {
40542
+ return { skip: null, successor: null };
40543
+ }
40544
+ const uniqueIds = [...new Set(ids)];
40545
+ const memories = uniqueIds.map((id) => getMemoryById(args.db, id));
40546
+ if (memories.some((memory) => !memory)) {
40547
+ return { skip: null, successor: null };
40548
+ }
40549
+ const successor = Number.isInteger(params.superseded_by) ? getMemoryById(args.db, params.superseded_by) : null;
40550
+ const refusals = memories.flatMap((memory) => {
40551
+ if (!memory)
40552
+ return [];
40553
+ const refusal = assessCurateMutationSafety({
40554
+ memory,
40555
+ verdict: params.action,
40556
+ reason: params.reason,
40557
+ replacementContent: content,
40558
+ successor,
40559
+ projectIdentity: (candidate) => projectIdentityForStoredPath(candidate.projectPath)
40560
+ });
40561
+ return refusal ? [refusal] : [];
40562
+ });
40563
+ if (refusals.length === 0)
40564
+ return { skip: null, successor };
40565
+ let refused = 0;
40566
+ for (const refusal of refusals) {
40567
+ refused = recordCurateSafetyRefusal(args.sessionId, refusal);
40568
+ }
40569
+ const memoryIds = refusals.map((refusal) => refusal.memoryId).join(", ");
40570
+ const reasons = [...new Set(refusals.map((refusal) => refusal.reason))].join(",");
40571
+ return {
40572
+ skip: `Skipped ${params.action} for memory [ID: ${memoryIds}]: curate safety refusal (${reasons}); refused=${refused}.`,
40573
+ successor
40574
+ };
40575
+ }
39878
40576
  function isPrimaryMutableMemory(memory) {
39879
40577
  return (memory.status === "active" || memory.status === "permanent") && memory.supersededByMemoryId === null;
39880
40578
  }
@@ -39901,7 +40599,10 @@ var ctxMemoryArgsShape = {
39901
40599
  limit: tool2.schema.number().optional().describe("Max results for list (default: 10)"),
39902
40600
  reason: tool2.schema.string().optional().describe("Why the memory is being archived (optional, recommended)")
39903
40601
  };
39904
- var ctxMemoryArgsSchema = tool2.schema.object(ctxMemoryArgsShape).passthrough();
40602
+ var ctxMemoryArgsSchema = tool2.schema.object({
40603
+ ...ctxMemoryArgsShape,
40604
+ superseded_by: tool2.schema.number().optional()
40605
+ }).passthrough();
39905
40606
  function createCtxMemoryTool(deps) {
39906
40607
  const allowedActions = getAllowedActions(deps);
39907
40608
  return tool2({
@@ -39916,7 +40617,8 @@ function createCtxMemoryTool(deps) {
39916
40617
  category: { type: "enum", values: V2_MEMORY_CATEGORIES },
39917
40618
  ids: { type: "array", items: "number", maxItems: 100 },
39918
40619
  limit: "number",
39919
- reason: "string"
40620
+ reason: "string",
40621
+ superseded_by: "number"
39920
40622
  });
39921
40623
  if (toolContext.agent === SIDEKICK_AGENT) {
39922
40624
  return "Error: ctx_memory is not available to the sidekick agent.";
@@ -39929,6 +40631,13 @@ function createCtxMemoryTool(deps) {
39929
40631
  return "Error: Could not resolve project identity for memory action.";
39930
40632
  }
39931
40633
  await deps.ensureProjectRegistered?.(toolContext.directory, deps.db);
40634
+ const curatePreflight = toolContext.agent === DREAMER_AGENT ? preflightCurateMutation({
40635
+ db: deps.db,
40636
+ params: args,
40637
+ sessionId: toolContext.sessionID
40638
+ }) : { skip: null, successor: null };
40639
+ if (curatePreflight.skip)
40640
+ return curatePreflight.skip;
39932
40641
  if (args.action !== "list") {
39933
40642
  const marker = getAuthorityManagedMarker(deps.db, projectPath);
39934
40643
  let authorityState = null;
@@ -39951,18 +40660,29 @@ function createCtxMemoryTool(deps) {
39951
40660
  }
39952
40661
  try {
39953
40662
  const commandId = toolCallIdFromContext(toolContext);
40663
+ const moduleArgs = args.action === "archive" && curatePreflight.successor ? {
40664
+ action: "merge",
40665
+ ids: [
40666
+ ...new Set([
40667
+ ...args.ids ?? [],
40668
+ curatePreflight.successor.id
40669
+ ])
40670
+ ],
40671
+ content: curatePreflight.successor.content,
40672
+ category: curatePreflight.successor.category
40673
+ } : args;
39954
40674
  const text = moduleMemoryText(await memoryBackend({
39955
40675
  ...commandId ? { commandId } : {},
39956
40676
  sessionId: toolContext.sessionID,
39957
40677
  projectRoot: toolContext.directory,
39958
40678
  projectPath,
39959
40679
  memoryProject: projectPath,
39960
- action: args.action,
39961
- content: args.content,
39962
- category: args.category,
39963
- ids: args.ids,
39964
- reason: args.reason
39965
- }), args);
40680
+ action: moduleArgs.action,
40681
+ content: moduleArgs.content,
40682
+ category: moduleArgs.category,
40683
+ ids: moduleArgs.ids,
40684
+ reason: moduleArgs.reason
40685
+ }), moduleArgs);
39966
40686
  return text ?? "Error: Rust module returned an invalid ctx_memory response.";
39967
40687
  } catch (error) {
39968
40688
  if (isRustAuthorityDrainingError(error)) {
@@ -40241,11 +40961,21 @@ function createCtxMemoryTool(deps) {
40241
40961
  runImmediateTransaction(deps.db, () => {
40242
40962
  for (const target of targets) {
40243
40963
  archiveMemory(deps.db, target.memoryId, args.reason);
40244
- queueMemoryMutation(deps.db, {
40245
- projectPath: target.projectIdentity,
40246
- mutationType: "archive",
40247
- targetMemoryId: target.memoryId
40248
- });
40964
+ if (toolContext.agent === DREAMER_AGENT && curatePreflight.successor) {
40965
+ supersededMemory(deps.db, target.memoryId, curatePreflight.successor.id);
40966
+ queueMemoryMutation(deps.db, {
40967
+ projectPath: target.projectIdentity,
40968
+ mutationType: "superseded",
40969
+ targetMemoryId: target.memoryId,
40970
+ supersededById: curatePreflight.successor.id
40971
+ });
40972
+ } else {
40973
+ queueMemoryMutation(deps.db, {
40974
+ projectPath: target.projectIdentity,
40975
+ mutationType: "archive",
40976
+ targetMemoryId: target.memoryId
40977
+ });
40978
+ }
40249
40979
  }
40250
40980
  });
40251
40981
  requestRustMemorySync(deps, toolContext.sessionID);
@@ -41280,11 +42010,16 @@ function createCtxReduceTool(deps) {
41280
42010
  const protectedTagIds = activeTags.map((tag) => tag.tagNumber).sort((left, right) => right - left).slice(0, deps.protectedTags);
41281
42011
  const protectedSet = new Set(protectedTagIds);
41282
42012
  const tagStatusMap = new Map(allTags.map((tag) => [tag.tagNumber, tag.status]));
42013
+ const inertWhitespaceTagNumbers = new Set(getInertWhitespaceAssistantTags(deps.db, sessionId).map((tag) => tag.tagNumber));
42014
+ const inertDropIds = [
42015
+ ...new Set(dropIds.filter((id) => inertWhitespaceTagNumbers.has(id)))
42016
+ ];
42017
+ const inertNote = inertDropIds.length > 0 ? `Skipped: ${inertDropIds.map((id) => `§${id}§ is provider framing, nothing to reclaim`).join("; ")}.` : "";
41283
42018
  const pendingOps = getPendingOps(deps.db, sessionId);
41284
42019
  const pendingMap = new Map(pendingOps.map((op) => [op.tagId, op.operation]));
41285
42020
  const conflicts = [];
41286
42021
  for (const id of dropIds) {
41287
- if (tagStatusMap.get(id) === "compacted") {
42022
+ if (tagStatusMap.get(id) === "compacted" && !inertWhitespaceTagNumbers.has(id)) {
41288
42023
  conflicts.push(`§${id}§ is from before compaction`);
41289
42024
  }
41290
42025
  }
@@ -41292,10 +42027,13 @@ function createCtxReduceTool(deps) {
41292
42027
  return `Error: Conflicting operations — ${conflicts.join("; ")}.`;
41293
42028
  }
41294
42029
  const preFilterDropCount = dropIds.length;
41295
- dropIds = dropIds.filter((id) => tagStatusMap.get(id) !== "dropped" && pendingMap.get(id) !== "drop");
42030
+ dropIds = dropIds.filter((id) => !inertWhitespaceTagNumbers.has(id) && tagStatusMap.get(id) !== "dropped" && pendingMap.get(id) !== "drop");
41296
42031
  const skippedCount = preFilterDropCount - dropIds.length;
41297
42032
  if (dropIds.length === 0) {
41298
- return "All requested tags were already queued or processed. No new action is needed.";
42033
+ return [
42034
+ inertNote,
42035
+ "All requested tags were already queued or processed. No new action is needed."
42036
+ ].filter(Boolean).join(" ");
41299
42037
  }
41300
42038
  try {
41301
42039
  deps.db.transaction(() => {
@@ -41318,7 +42056,7 @@ function createCtxReduceTool(deps) {
41318
42056
  parts.push(`drop ${formatIds(immediateDropIds)}`);
41319
42057
  if (deferredDropIds.length > 0)
41320
42058
  parts.push(`deferred drop ${formatIds(deferredDropIds)}`);
41321
- return `Queued: ${parts.join(", ")}.${skippedNote}`;
42059
+ return `Queued: ${parts.join(", ")}.${skippedNote}${inertNote ? ` ${inertNote}` : ""}`;
41322
42060
  }
41323
42061
  });
41324
42062
  }
@@ -41379,90 +42117,6 @@ function normalizeSources(sources) {
41379
42117
  }
41380
42118
  return result;
41381
42119
  }
41382
- function formatAge2(committedAtMs) {
41383
- const ageMs = Date.now() - committedAtMs;
41384
- if (ageMs < 0)
41385
- return "future";
41386
- const days = Math.floor(ageMs / (24 * 60 * 60 * 1000));
41387
- if (days <= 0)
41388
- return "today";
41389
- if (days === 1)
41390
- return "1d ago";
41391
- if (days < 30)
41392
- return `${days}d ago`;
41393
- const months = Math.floor(days / 30);
41394
- if (months === 1)
41395
- return "1mo ago";
41396
- if (months < 12)
41397
- return `${months}mo ago`;
41398
- const years = Math.floor(days / 365);
41399
- return years === 1 ? "1y ago" : `${years}y ago`;
41400
- }
41401
- var NOTE_EXPAND_HINT = "Use ctx_expand(start=N-10, end=N) around any note @msg anchor above to read the surrounding conversation context.";
41402
- function formatResult(result, index, currentSessionId) {
41403
- if (result.source === "memory") {
41404
- const source = result.sourceName ? ` source=${result.sourceName}` : "";
41405
- return [
41406
- `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category}${source} match=${result.matchType}`,
41407
- result.content
41408
- ].join(`
41409
- `);
41410
- }
41411
- if (result.source === "git_commit") {
41412
- return [
41413
- `[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatAge2(result.committedAtMs)} match=${result.matchType}`,
41414
- result.content
41415
- ].join(`
41416
- `);
41417
- }
41418
- if (result.source === "primer") {
41419
- return [
41420
- `[${index}] [primer] score=${result.score.toFixed(2)} id=${result.primerId} support=${result.support} match=${result.matchType}`,
41421
- result.content
41422
- ].join(`
41423
- `);
41424
- }
41425
- if (result.source === "note") {
41426
- const anchor = result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId ? ` @msg ${result.anchorOrdinal}` : "";
41427
- return [
41428
- `[${index}] [note] score=${result.score.toFixed(2)} id=#${result.noteId} status=${result.status} ${formatAge2(result.createdAt)}${anchor}`,
41429
- result.content
41430
- ].join(`
41431
- `);
41432
- }
41433
- if (result.source === "compartment") {
41434
- return [
41435
- `[${index}] [message] score=${result.score.toFixed(2)} compartment_id=${result.compartmentId} range=${result.startOrdinal}-${result.endOrdinal} match=${result.matchType} title=${result.title}`,
41436
- result.snippet ? `Snippet: ${result.snippet}` : result.content
41437
- ].join(`
41438
- `);
41439
- }
41440
- const expandStart = Math.max(1, result.messageOrdinal - 3);
41441
- const expandEnd = result.messageOrdinal + 3;
41442
- return [
41443
- `[${index}] [message] score=${result.score.toFixed(2)} ordinal=${result.messageOrdinal} range=${expandStart}-${expandEnd} role=${result.role}`,
41444
- result.content
41445
- ].join(`
41446
- `);
41447
- }
41448
- function formatSearchResults(query, results, currentSessionId) {
41449
- if (results.length === 0) {
41450
- return `No results found for "${query}" across notes, memories, primers, git commits, or message history.`;
41451
- }
41452
- const bodyParts = results.map((result, index) => formatResult(result, index + 1, currentSessionId));
41453
- if (results.some((result) => result.source === "message" || result.source === "compartment")) {
41454
- bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
41455
- }
41456
- if (results.some((result) => result.source === "note" && result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId)) {
41457
- bodyParts.push(NOTE_EXPAND_HINT);
41458
- }
41459
- const body = bodyParts.join(`
41460
-
41461
- `);
41462
- return `Found ${results.length} result${results.length === 1 ? "" : "s"} for "${query}":
41463
-
41464
- ${body}`;
41465
- }
41466
42120
  var ctxSearchArgsShape = {
41467
42121
  query: tool5.schema.string().optional().describe("Search query. Matches against memory content, Primers, git commit messages, and raw user/assistant message text."),
41468
42122
  limit: tool5.schema.number().optional().describe("Maximum results to return (default: 10)"),
@@ -41493,6 +42147,7 @@ function createCtxSearchTool(deps) {
41493
42147
  const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, toolContext.sessionID);
41494
42148
  const messageOrdinalCutoff = lastCompartmentEnd >= 0 ? lastCompartmentEnd : 0;
41495
42149
  const visibleMemoryIds = getVisibleMemoryIds(deps.db, toolContext.sessionID);
42150
+ const diagnostics = createUnifiedSearchDiagnostics();
41496
42151
  const projectPath = deps.resolveProjectPath(toolContext.directory);
41497
42152
  if (!projectPath) {
41498
42153
  return "Error: Could not resolve project identity for search.";
@@ -41509,10 +42164,11 @@ function createCtxSearchTool(deps) {
41509
42164
  projectPath,
41510
42165
  ids: idShape,
41511
42166
  limit: Math.max(normalizeLimit3(args.limit), idShape.length),
41512
- visibleMemoryIds
42167
+ visibleMemoryIds,
42168
+ diagnostics
41513
42169
  });
41514
- if (idResults !== null) {
41515
- return formatSearchResults(query, idResults, toolContext.sessionID);
42170
+ if (idResults !== null || diagnostics.suppressedVisibleMemoryIds.length > 0) {
42171
+ return formatSearchResults(query, idResults ?? [], toolContext.sessionID, diagnostics);
41516
42172
  }
41517
42173
  }
41518
42174
  const results = await unifiedSearch(deps.db, toolContext.sessionID, projectPath, query, {
@@ -41529,9 +42185,11 @@ function createCtxSearchTool(deps) {
41529
42185
  gitCommitsEnabled,
41530
42186
  sources: normalizeSources(args.sources),
41531
42187
  visibleMemoryIds,
42188
+ diagnostics,
42189
+ gitRepositoryAvailable: typeof toolContext.directory === "string" ? directoryHasGitMetadata(toolContext.directory) : undefined,
41532
42190
  explicitSearch: true
41533
42191
  });
41534
- return formatSearchResults(query, results, toolContext.sessionID);
42192
+ return formatSearchResults(query, results, toolContext.sessionID, diagnostics);
41535
42193
  }
41536
42194
  });
41537
42195
  }
@@ -41945,6 +42603,7 @@ var server = async (ctx) => {
41945
42603
  });
41946
42604
  const loadedPluginConfig = loadPluginConfigDetailed(ctx.directory);
41947
42605
  const pluginConfig = loadedPluginConfig.config;
42606
+ reloadWindowOverlay(pluginConfig.models?.window_overlay_path);
41948
42607
  const promptSurfaceRuntime = createPromptSurfaceRuntime({
41949
42608
  harness: "opencode",
41950
42609
  directory: ctx.directory,
@@ -41980,7 +42639,7 @@ var server = async (ctx) => {
41980
42639
  `);
41981
42640
  setTimeout(async () => {
41982
42641
  try {
41983
- const { sendIgnoredMessage: sendIgnoredMessage2 } = await import("./send-session-notification-kn34g7mx.js");
42642
+ const { sendIgnoredMessage: sendIgnoredMessage2 } = await import("./send-session-notification-bw7eex6w.js");
41984
42643
  const clientWithSessions = ctx.client;
41985
42644
  const sessions = await Promise.resolve(clientWithSessions.session?.list?.()).catch(() => null);
41986
42645
  const sessionList = Array.isArray(sessions) ? sessions : sessions?.data;
@@ -42009,7 +42668,9 @@ var server = async (ctx) => {
42009
42668
  }
42010
42669
  }
42011
42670
  const liveSessionState = createLiveSessionState();
42012
- const rustModeModuleClient = pluginConfig.transform_mode === "rust" ? new SubcModuleTransport(pluginConfig.subc?.connection_file) : undefined;
42671
+ const rustModeModuleTransport = pluginConfig.transform_mode === "rust" ? new SubcModuleTransport(pluginConfig.subc?.connection_file) : undefined;
42672
+ const rustModeModuleClient = rustModeModuleTransport;
42673
+ const sessionCleanupModuleClient = pluginConfig.enabled ? rustModeModuleTransport ?? new SubcModuleTransport(pluginConfig.subc?.connection_file) : undefined;
42013
42674
  const hooks = await createSessionHooksAsync({
42014
42675
  ctx,
42015
42676
  pluginConfig,
@@ -42149,7 +42810,8 @@ var server = async (ctx) => {
42149
42810
  liveSessionState.dreamerProgressByProject.delete(timerProjectIdentity);
42150
42811
  }
42151
42812
  },
42152
- moduleClient: classifyModuleClient
42813
+ moduleClient: classifyModuleClient,
42814
+ sessionCleanupModuleClient
42153
42815
  };
42154
42816
  try {
42155
42817
  stopDreamTimerRegistration = await startDreamScheduleTimer(timerRegistration);
@@ -42173,7 +42835,7 @@ var server = async (ctx) => {
42173
42835
  {
42174
42836
  const fence = getSchemaFenceRejection();
42175
42837
  if (fence) {
42176
- import("./conflict-warning-hook-xj1r88a7.js").then(({ sendSchemaFenceWarning }) => sendSchemaFenceWarning(ctx.client, ctx.directory, fence));
42838
+ import("./conflict-warning-hook-rf17b6et.js").then(({ sendSchemaFenceWarning }) => sendSchemaFenceWarning(ctx.client, ctx.directory, fence));
42177
42839
  }
42178
42840
  }
42179
42841
  if (conflictResult?.hasConflict) {
@@ -42194,7 +42856,7 @@ var server = async (ctx) => {
42194
42856
  } = await import("./announcement-3g9xg433.js");
42195
42857
  if (shouldShowAnnouncement2()) {
42196
42858
  setTimeout(() => {
42197
- import("./conflict-warning-hook-xj1r88a7.js").then(({ sendStartupAnnouncement }) => sendStartupAnnouncement(ctx.client, ctx.directory, ANNOUNCEMENT_VERSION2, ANNOUNCEMENT_FEATURES2, ANNOUNCEMENT_FOOTER2, markAnnouncementSeen2)).catch(() => {});
42859
+ import("./conflict-warning-hook-rf17b6et.js").then(({ sendStartupAnnouncement }) => sendStartupAnnouncement(ctx.client, ctx.directory, ANNOUNCEMENT_VERSION2, ANNOUNCEMENT_FEATURES2, ANNOUNCEMENT_FOOTER2, markAnnouncementSeen2)).catch(() => {});
42198
42860
  }, 8000);
42199
42861
  }
42200
42862
  } catch {}