@retaw106/mmc-oc 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/cli.js +12 -1
  2. package/dist/config/schema/magic-context.d.ts +33 -0
  3. package/dist/config/schema/magic-context.d.ts.map +1 -1
  4. package/dist/config-init.js +12 -1
  5. package/dist/{context-wt7g4f8k.js → context-c72brxe5.js} +717 -206
  6. package/dist/features/magic-context/memory-maintainer/launcher.d.ts +13 -4
  7. package/dist/features/magic-context/memory-maintainer/launcher.d.ts.map +1 -1
  8. package/dist/features/magic-context/memory-maintainer/manual-commands.d.ts.map +1 -1
  9. package/dist/features/magic-context/memory-maintainer/model-chain.d.ts +47 -0
  10. package/dist/features/magic-context/memory-maintainer/model-chain.d.ts.map +1 -0
  11. package/dist/features/magic-context/memory-maintainer/oc2-adapter.d.ts +17 -4
  12. package/dist/features/magic-context/memory-maintainer/oc2-adapter.d.ts.map +1 -1
  13. package/dist/features/magic-context/memory-maintainer/pipeline.d.ts +6 -0
  14. package/dist/features/magic-context/memory-maintainer/pipeline.d.ts.map +1 -1
  15. package/dist/features/magic-context/storage-tags.d.ts +4 -0
  16. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  17. package/dist/features/magic-context/transform-decision-log.d.ts +57 -2
  18. package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
  19. package/dist/hooks/magic-context/__fixtures__/tail-hygiene-refresh.d.ts +34 -0
  20. package/dist/hooks/magic-context/__fixtures__/tail-hygiene-refresh.d.ts.map +1 -0
  21. package/dist/hooks/magic-context/apply-operations.d.ts +3 -1
  22. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  23. package/dist/hooks/magic-context/compartment-runner-types.d.ts +2 -2
  24. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  25. package/dist/hooks/magic-context/compartment-runner.d.ts +4 -0
  26. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  27. package/dist/hooks/magic-context/compartment-trigger-error.d.ts +11 -0
  28. package/dist/hooks/magic-context/compartment-trigger-error.d.ts.map +1 -0
  29. package/dist/hooks/magic-context/compartment-trigger.d.ts +1 -1
  30. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  31. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts +1 -0
  32. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts.map +1 -1
  33. package/dist/hooks/magic-context/degraded-pass-refusal.d.ts +2 -0
  34. package/dist/hooks/magic-context/degraded-pass-refusal.d.ts.map +1 -1
  35. package/dist/hooks/magic-context/edit-marker.d.ts +2 -0
  36. package/dist/hooks/magic-context/edit-marker.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +2 -0
  38. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
  39. package/dist/hooks/magic-context/pending-drop-eligibility.d.ts +14 -0
  40. package/dist/hooks/magic-context/pending-drop-eligibility.d.ts.map +1 -0
  41. package/dist/hooks/magic-context/pending-materialization.d.ts +2 -0
  42. package/dist/hooks/magic-context/pending-materialization.d.ts.map +1 -1
  43. package/dist/hooks/magic-context/provider-visible-parts.d.ts +10 -4
  44. package/dist/hooks/magic-context/provider-visible-parts.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/render-options.d.ts +19 -0
  46. package/dist/hooks/magic-context/render-options.d.ts.map +1 -0
  47. package/dist/hooks/magic-context/supersession-reclaim.d.ts +5 -3
  48. package/dist/hooks/magic-context/supersession-reclaim.d.ts.map +1 -1
  49. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/tail-hygiene-output-count.d.ts +18 -0
  51. package/dist/hooks/magic-context/tail-hygiene-output-count.d.ts.map +1 -0
  52. package/dist/hooks/magic-context/tail-hygiene-walk.d.ts +8 -4
  53. package/dist/hooks/magic-context/tail-hygiene-walk.d.ts.map +1 -1
  54. package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/tool-reclaim.d.ts +5 -2
  56. package/dist/hooks/magic-context/tool-reclaim.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  58. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +2 -0
  59. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  60. package/dist/hooks/magic-context/transform.d.ts +0 -7
  61. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  62. package/dist/hooks/magic-context/unresolved-history-boundary.d.ts +5 -1
  63. package/dist/hooks/magic-context/unresolved-history-boundary.d.ts.map +1 -1
  64. package/dist/{index-3gj1ak4c.js → index-27ddyx39.js} +1 -1
  65. package/dist/{index-z2w1eq3y.js → index-48e9znrc.js} +491 -197
  66. package/dist/{index-z2g20msd.js → index-azbmnekv.js} +4 -4
  67. package/dist/{index-czba8tw3.js → index-dwh99z66.js} +33 -10
  68. package/dist/{index-kw21z5pe.js → index-kahkxcqw.js} +7005 -6903
  69. package/dist/{index-a5mw0xp3.js → index-r7s7d350.js} +1 -1
  70. package/dist/{index-18yrysyj.js → index-ykq24x1n.js} +1 -1
  71. package/dist/index.js +2 -2
  72. package/dist/{message-time-backfill-sykevdax.js → message-time-backfill-j96x1sab.js} +1 -1
  73. package/dist/model-resolution-cvyrrcjw.js +8 -0
  74. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  75. package/dist/{read-session-chunk-863d6c06.js → read-session-chunk-t8bg6wkz.js} +3 -3
  76. package/dist/{recomp-orchestrator-wrjy3qym.js → recomp-orchestrator-k760qy1n.js} +6 -6
  77. package/dist/{send-session-notification-cz6ev088.js → send-session-notification-njjf3b4s.js} +3 -3
  78. package/dist/shared/model-suggestion-retry.d.ts +21 -0
  79. package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
  80. package/dist/shared/prompt-surface-runtime.d.ts +5 -0
  81. package/dist/shared/prompt-surface-runtime.d.ts.map +1 -1
  82. package/dist/shared/reclaim-distance.d.ts +33 -0
  83. package/dist/shared/reclaim-distance.d.ts.map +1 -0
  84. package/dist/shared/sqlite.d.ts +2 -0
  85. package/dist/shared/sqlite.d.ts.map +1 -1
  86. package/dist/shared/stable-json.d.ts +7 -0
  87. package/dist/shared/stable-json.d.ts.map +1 -1
  88. package/dist/shared/tail-hygiene-json.d.ts +22 -0
  89. package/dist/shared/tail-hygiene-json.d.ts.map +1 -0
  90. package/dist/shared/user-facing-codes.d.ts +2 -2
  91. package/dist/v2/hidden-completion.d.ts +20 -1
  92. package/dist/v2/hidden-completion.d.ts.map +1 -1
  93. package/dist/v2/hooks/context.d.ts.map +1 -1
  94. package/dist/v2/hooks/hidden-child.d.ts +22 -1
  95. package/dist/v2/hooks/hidden-child.d.ts.map +1 -1
  96. package/dist/v2/server.js +8312 -7354
  97. package/dist/v2/store-reader.d.ts +27 -1
  98. package/dist/v2/store-reader.d.ts.map +1 -1
  99. package/dist/web/main.js +34 -10
  100. package/dist/{wrapup-orchestrator-bmybc5p5.js → wrapup-orchestrator-b2gswatj.js} +6 -6
  101. package/package.json +1 -1
  102. package/src/config/latch-permanence-guard.test.ts +4 -0
  103. package/src/config/project-security.test.ts +17 -0
  104. package/src/config/schema/magic-context.test.ts +50 -0
  105. package/src/config/schema/magic-context.ts +34 -1
  106. package/src/shared/model-suggestion-retry.ts +5 -1
  107. package/src/shared/prompt-surface-runtime.ts +12 -0
  108. package/src/shared/reclaim-distance.test.ts +111 -0
  109. package/src/shared/reclaim-distance.ts +91 -0
  110. package/src/shared/root-async-context.probe.mjs +36 -0
  111. package/src/shared/root-async-context.test.ts +49 -0
  112. package/src/shared/sqlite-busy-budget-review.test.ts +291 -0
  113. package/src/shared/sqlite-foreground-wait.test.ts +237 -1
  114. package/src/shared/sqlite.ts +55 -14
  115. package/src/shared/stable-json.ts +56 -14
  116. package/src/shared/tail-hygiene-json.test.ts +117 -0
  117. package/src/shared/tail-hygiene-json.ts +217 -0
  118. package/src/shared/user-facing-codes.ts +2 -2
  119. package/dist/model-resolution-wazv26n0.js +0 -6
@@ -34,6 +34,19 @@ import {
34
34
  resolveProjectIdentityForSession,
35
35
  normalizeStoredProjectPath,
36
36
  storedPathBelongsToIdentity,
37
+ parseProviderModel,
38
+ modelBodyField,
39
+ toModelEntry,
40
+ HiddenCompletionRefusal,
41
+ HIDDEN_HISTORIAN_AGENT,
42
+ resolveHistorianStepCap,
43
+ HIDDEN_RUN_DISPOSED_REASON,
44
+ noteChildOfDisposedInstance,
45
+ hiddenChildPermissions,
46
+ registerHiddenChildAgents,
47
+ HiddenAgentStepLimit,
48
+ HiddenChildHook,
49
+ isNonRetryable,
37
50
  V2_MEMORY_CATEGORIES,
38
51
  CATEGORY_PRIORITY,
39
52
  computeNormalizedHash,
@@ -97,6 +110,7 @@ import {
97
110
  HYGIENE_PROVIDER_UNITS_VERSION,
98
111
  sessionDecisionCalibration,
99
112
  transitionSessionHygieneUnits,
113
+ readEpochFloorSnapshot,
100
114
  getProtectionWindowForSession,
101
115
  ReplayDocumentError,
102
116
  readReplayEnvelope,
@@ -183,6 +197,7 @@ import {
183
197
  clearSession,
184
198
  requestPendingMaterialization,
185
199
  pendingMaterializationOrigin,
200
+ hasPendingMaterializationOrigin,
186
201
  clearPendingMaterialization,
187
202
  recordSystemPromptForRequest,
188
203
  recordHeldHeadForRequest,
@@ -242,18 +257,10 @@ import {
242
257
  sourceNameForMemory,
243
258
  rebaseSessionCoordinatesAsync,
244
259
  renderUserFacingFailure,
260
+ userFacingFailureCode,
245
261
  renderCapabilityRefusal,
246
- HiddenCompletionRefusal,
247
- HIDDEN_HISTORIAN_AGENT,
248
- resolveHistorianStepCap,
249
- hiddenChildPermissions,
250
- registerHiddenChildAgents,
251
- HiddenAgentStepLimit,
252
- HiddenChildHook,
253
- parseProviderModel,
254
- toModelEntry,
255
262
  ACTIVE_TOOL_IDS,
256
- promptSurfaceHashMaterial,
263
+ systemPromptFingerprint,
257
264
  createPromptSurfaceRuntime,
258
265
  createPromptSurfaceGuidanceEpochCache,
259
266
  getOpenCodeDbPath,
@@ -294,7 +301,7 @@ import {
294
301
  recordHighPressureNoEligibleHead,
295
302
  getActiveCompartmentRun,
296
303
  startCompartmentAgent
297
- } from "./index-kw21z5pe.js";
304
+ } from "./index-kahkxcqw.js";
298
305
  import {
299
306
  getHarness,
300
307
  getDataDir,
@@ -326,6 +333,16 @@ import {
326
333
  import {
327
334
  normalizeProtectedToolName,
328
335
  mergeProtectedTools,
336
+ detectLatestTurnThinkingMismatch,
337
+ isPrefixBoundThinkingModel,
338
+ detectOverflow,
339
+ stripWellFormedLeadingTagPrefix,
340
+ stripPersistedAssistantText,
341
+ byteSize,
342
+ stripTagPrefix,
343
+ peelLeadingMcTagNotation,
344
+ prependTag,
345
+ isThinkingPart,
329
346
  isRecord,
330
347
  protectedToolTagNumbers,
331
348
  protectedToolTokenCount,
@@ -335,13 +352,7 @@ import {
335
352
  preloadTokenizer,
336
353
  estimateTokens,
337
354
  estimateFixedPromptTokens,
338
- stripWellFormedLeadingTagPrefix,
339
- stripPersistedAssistantText,
340
- byteSize,
341
- stripTagPrefix,
342
- peelLeadingMcTagNotation,
343
- prependTag,
344
- isThinkingPart,
355
+ currentReclaimableToolOutputCount,
345
356
  contentTagOwnerMessageId,
346
357
  estimateImageTokensFromDataUrl,
347
358
  estimateToolAttachmentImageTokens,
@@ -374,6 +385,7 @@ import {
374
385
  getPendingOpsCount,
375
386
  clearPendingOps,
376
387
  removePendingOp,
388
+ tagRevisionKey,
377
389
  tagIdentityRevisionKey,
378
390
  getRecentTagOwnerMessageIds,
379
391
  AGE_RECLAIM_MIN_TOKENS,
@@ -390,6 +402,7 @@ import {
390
402
  backfillTagTokenCounts,
391
403
  insertTag,
392
404
  updateTagStatus,
405
+ markTagDropped,
393
406
  markWhitespaceAssistantTagInert,
394
407
  getInertWhitespaceAssistantTags,
395
408
  getInertWhitespaceAssistantTagsByNumbers,
@@ -431,13 +444,11 @@ import {
431
444
  recordToolDefinition,
432
445
  getMeasuredToolDefinitionTokens,
433
446
  describeFinalWireTail,
447
+ estimatePartImageTokens,
434
448
  estimateMessageTokens,
435
449
  wireContentBytes,
436
450
  createFinalWireUsageTracker,
437
451
  estimateFinalWireInputTokens,
438
- detectLatestTurnThinkingMismatch,
439
- isPrefixBoundThinkingModel,
440
- detectOverflow,
441
452
  modelAcceptsEmptyContent,
442
453
  isAnthropicFamilyRoute,
443
454
  neutralizeDroppedReasoningPart,
@@ -463,9 +474,14 @@ import {
463
474
  primeTailRawMessageCache2,
464
475
  primeInMemoryTailRawMessageCache2,
465
476
  readRawSessionMessageIdOrdinalsForRange2,
477
+ hasRawSessionMessageById2,
466
478
  readRawSessionMessageById2,
467
479
  readSessionChunk2
468
- } from "./index-z2w1eq3y.js";
480
+ } from "./index-48e9znrc.js";
481
+ import {
482
+ normalizeModelEntry2,
483
+ resolveHistorianModel2
484
+ } from "./index-azbmnekv.js";
469
485
  import {
470
486
  getSqliteMemoryStats,
471
487
  withSqliteBackgroundWriter,
@@ -476,7 +492,7 @@ import {
476
492
  withAsyncPrivilegedWriter,
477
493
  indexCoordinationInput,
478
494
  withIndexChunk
479
- } from "./index-czba8tw3.js";
495
+ } from "./index-dwh99z66.js";
480
496
  import {
481
497
  canonicalModelIdentity,
482
498
  piModelRefToCanonical,
@@ -493,7 +509,7 @@ import {
493
509
  latestRealUserTurnIdFromOpenCodeDb,
494
510
  getMessageTimesFromOpenCodeDb,
495
511
  findLastAssistantModelFromOpenCodeDb
496
- } from "./index-18yrysyj.js";
512
+ } from "./index-ykq24x1n.js";
497
513
  import {
498
514
  registerNotificationSink2,
499
515
  pushNotification2,
@@ -506,10 +522,7 @@ import {
506
522
  getErrorMessage,
507
523
  flushIgnoredMessages2,
508
524
  sendStatusNotification2
509
- } from "./index-a5mw0xp3.js";
510
- import {
511
- resolveHistorianModel2
512
- } from "./index-z2g20msd.js";
525
+ } from "./index-r7s7d350.js";
513
526
  import {
514
527
  logSlowWriteTransaction
515
528
  } from "./index-yjv8dpht.js";
@@ -3159,6 +3172,40 @@ function currentPluginConfigReader(directory) {
3159
3172
  return pluginReaders.get(directory);
3160
3173
  }
3161
3174
 
3175
+ // src/features/magic-context/memory-maintainer/model-chain.ts
3176
+ var MAINTENANCE_MODEL_UNCONFIGURED_MESSAGE = "memory maintenance has no primary model configured — set maintenance.model or historian.model in the user-level mmc config (fallback_models alone does not enable maintenance; the session model is never inherited)";
3177
+ function inheritMaintenanceModelConfig(config) {
3178
+ const { maintenance, historian } = config;
3179
+ if (maintenance.model !== undefined || historian?.model === undefined)
3180
+ return maintenance;
3181
+ const entry = typeof historian.model === "string" ? { model: historian.model } : historian.model;
3182
+ return {
3183
+ ...maintenance,
3184
+ model: {
3185
+ ...entry,
3186
+ variant: entry.variant ?? historian.variant,
3187
+ thinking_level: entry.thinking_level ?? historian.thinking_level
3188
+ },
3189
+ fallback_models: maintenance.fallback_models ?? historian.fallback_models
3190
+ };
3191
+ }
3192
+ function resolveMaintenanceModelChain(config, harness) {
3193
+ const primary = normalizeModelEntry2(config.model, harness);
3194
+ if (!primary)
3195
+ return;
3196
+ const fallbacks = [];
3197
+ for (const entry of config.fallback_models ?? []) {
3198
+ const resolved = normalizeModelEntry2(entry, harness);
3199
+ if (resolved)
3200
+ fallbacks.push(resolved);
3201
+ }
3202
+ return { primary, fallbacks };
3203
+ }
3204
+ function hasMaintenancePrimary(config) {
3205
+ const primary = toModelEntry(config.model);
3206
+ return primary !== undefined && primary.model.trim().length > 0;
3207
+ }
3208
+
3162
3209
  // src/features/magic-context/memory-maintainer/dream-state.ts
3163
3210
  var getDreamStateStatements = new WeakMap;
3164
3211
  var setDreamStateStatements = new WeakMap;
@@ -4500,6 +4547,7 @@ async function executeGuardedRun(opts, holderId, leaseKey, acquisition) {
4500
4547
  pending,
4501
4548
  fullPool: roundState.snapshot,
4502
4549
  shouldContinue: () => meter.shouldContinue(),
4550
+ remainingWallMs: () => Math.max(0, config.budgets.wallMs - meter.getState().spent.wallMs),
4503
4551
  recordUsage: (delta) => meter.recordUsage(delta),
4504
4552
  recordToolStep: () => meter.recordToolStep(),
4505
4553
  refuseToolStep: () => meter.refuseToolStep()
@@ -4798,49 +4846,86 @@ function transcriptFromCompletion(completion, createdAt) {
4798
4846
  }
4799
4847
  ];
4800
4848
  }
4801
- async function runOc2MaintenancePhase(ctx, phase, deps, stageAManifest = "") {
4802
- if (!deps.executor)
4803
- return emptyPhaseResult();
4804
- const agentId = phase === "A" ? MEMORY_MAINTAINER_A_AGENT : MEMORY_MAINTAINER_B_AGENT;
4805
- const system = phase === "A" ? MEMORY_MAINTAINER_A_SYSTEM_PROMPT : MEMORY_MAINTAINER_B_SYSTEM_PROMPT;
4806
- const basePrompt = buildPhasePrompt(ctx, phase, deps, stageAManifest);
4807
- let manifestText = "";
4808
- let messages;
4809
- for (let round = 0;round < 2; round += 1) {
4810
- const decision = ctx.shouldContinue();
4811
- if (decision === "stop")
4812
- break;
4813
- const prompt = decision === "finalize" ? `${MEMORY_MAINTAINER_FINALIZE_INSTRUCTION}
4814
-
4815
- ${basePrompt}` : basePrompt;
4849
+ async function attemptPhaseModelChain(ctx, phase, executor, deps, agentId, system, prompt) {
4850
+ const chain = [deps.modelChain.primary, ...deps.modelChain.fallbacks];
4851
+ let lastError;
4852
+ for (let index = 0;index < chain.length; index += 1) {
4853
+ const entry = toModelEntry(chain[index]);
4854
+ if (!entry || entry.model.trim().length === 0)
4855
+ continue;
4856
+ if (!parseProviderModel(entry.model)) {
4857
+ if (index === 0) {
4858
+ throw new Error(`memory maintenance: primary model "${entry.model}" is not in provider/model form — the hidden executor cannot use it and no model substitution is allowed`);
4859
+ }
4860
+ log(`[magic-context][maintenance] phase ${phase}: dropping invalid fallback spec: ${entry.model}`);
4861
+ continue;
4862
+ }
4863
+ if (index > 0) {
4864
+ if (ctx.shouldContinue() !== "continue")
4865
+ throw lastError;
4866
+ log(`[magic-context][maintenance] phase ${phase}: switching to fallback model ${entry.model} (attempt ${index + 1}/${chain.length})`);
4867
+ }
4816
4868
  const identity = {
4817
4869
  parentSessionId: deps.parentSessionId(),
4818
4870
  kind: "memory-maintenance",
4819
4871
  agent: agentId,
4820
4872
  system,
4821
- timeoutMs: deps.budget.wallMs,
4873
+ model: entry,
4874
+ timeoutMs: Math.max(1000, Math.min(deps.budget.wallMs, ctx.remainingWallMs())),
4822
4875
  title: `magic-context-maintainer-${phase.toLowerCase()}`,
4823
4876
  directory: deps.directory() ?? ""
4824
4877
  };
4825
- const handle = await deps.executor.open(identity);
4826
- let completion;
4878
+ const handle = await executor.open(identity);
4827
4879
  let promptSettled = false;
4828
4880
  try {
4829
4881
  const request = {
4830
4882
  path: { id: handle.id },
4831
- body: { parts: [{ type: "text", text: prompt }] }
4883
+ body: {
4884
+ parts: [{ type: "text", text: prompt }],
4885
+ ...modelBodyField(entry)
4886
+ }
4832
4887
  };
4833
- await deps.executor.attempt(handle, request);
4834
- completion = await deps.executor.collect(handle, 50);
4888
+ try {
4889
+ await executor.attempt(handle, request);
4890
+ } catch (error) {
4891
+ lastError = error;
4892
+ if (index >= chain.length - 1 || isNonRetryable(error))
4893
+ throw error;
4894
+ log(`[magic-context][maintenance] phase ${phase}: model ${entry.model} failed: ${error instanceof Error ? error.message : String(error)}; trying next fallback`);
4895
+ continue;
4896
+ }
4897
+ const completion = await executor.collect(handle, 50);
4835
4898
  promptSettled = true;
4899
+ return completion;
4836
4900
  } finally {
4837
- await deps.executor.close(handle, {
4901
+ await executor.close(handle, {
4838
4902
  promptSettled,
4839
4903
  privacySensitive: false,
4840
4904
  context: "memory-maintenance",
4841
4905
  log: (message) => log(`[magic-context][maintenance] ${message}`)
4842
4906
  });
4843
4907
  }
4908
+ }
4909
+ if (lastError !== undefined)
4910
+ throw lastError;
4911
+ return;
4912
+ }
4913
+ async function runOc2MaintenancePhase(ctx, phase, deps, stageAManifest = "") {
4914
+ if (!deps.executor)
4915
+ return emptyPhaseResult();
4916
+ const agentId = phase === "A" ? MEMORY_MAINTAINER_A_AGENT : MEMORY_MAINTAINER_B_AGENT;
4917
+ const system = phase === "A" ? MEMORY_MAINTAINER_A_SYSTEM_PROMPT : MEMORY_MAINTAINER_B_SYSTEM_PROMPT;
4918
+ const basePrompt = buildPhasePrompt(ctx, phase, deps, stageAManifest);
4919
+ let manifestText = "";
4920
+ let messages;
4921
+ for (let round = 0;round < 2; round += 1) {
4922
+ const decision = ctx.shouldContinue();
4923
+ if (decision === "stop")
4924
+ break;
4925
+ const prompt = decision === "finalize" ? `${MEMORY_MAINTAINER_FINALIZE_INSTRUCTION}
4926
+
4927
+ ${basePrompt}` : basePrompt;
4928
+ const completion = await attemptPhaseModelChain(ctx, phase, deps.executor, deps, agentId, system, prompt);
4844
4929
  if (completion) {
4845
4930
  const inputTotal = completion.usage.input + completion.usage.cacheRead + completion.usage.cacheWrite;
4846
4931
  ctx.recordUsage({ inputTokens: inputTotal, outputTokens: completion.usage.output });
@@ -4898,9 +4983,18 @@ function createOc2MaintenanceRunFactory(options) {
4898
4983
  if (!options.executor) {
4899
4984
  throw new Error("memory maintenance: no hidden completion executor wired");
4900
4985
  }
4986
+ const chain = resolveMaintenanceModelChain(options.config(), "opencode");
4987
+ if (!chain) {
4988
+ throw new Error(MAINTENANCE_MODEL_UNCONFIGURED_MESSAGE);
4989
+ }
4990
+ const primarySpec = toModelEntry(chain.primary)?.model ?? "";
4991
+ if (!parseProviderModel(primarySpec)) {
4992
+ throw new Error(`memory maintenance: primary model "${primarySpec}" is not in provider/model form — the hidden executor cannot use it and no model substitution is allowed`);
4993
+ }
4901
4994
  const cfg = options.config();
4902
4995
  const phaseDeps = {
4903
4996
  executor: options.executor,
4997
+ modelChain: chain,
4904
4998
  budget: cfg.budgets,
4905
4999
  parentSessionId: () => signals.parentSessionId,
4906
5000
  directory: () => signals.directory,
@@ -6160,6 +6254,20 @@ function resolveTodowriteAvailability(sessionId) {
6160
6254
  }
6161
6255
 
6162
6256
  // src/hooks/magic-context/degraded-pass-refusal.ts
6257
+ function describeDegradedPassFailure(site, cause) {
6258
+ const details = [site];
6259
+ const seen = new Set;
6260
+ while (cause !== undefined && cause !== null && !seen.has(cause)) {
6261
+ seen.add(cause);
6262
+ const current = cause;
6263
+ const detail = typeof current.site === "string" ? current.site : typeof current.message === "string" ? current.message : String(cause);
6264
+ if (!details.includes(detail))
6265
+ details.push(detail);
6266
+ cause = current.cause;
6267
+ }
6268
+ return `Magic Context could not safely prepare this turn [${userFacingFailureCode("transform_pass_degraded")}] (${details.join(": ")}). This request was not sent.`;
6269
+ }
6270
+
6163
6271
  class DegradedPassRefusalError extends Error {
6164
6272
  site;
6165
6273
  code = "DEGRADED_PASS_REFUSAL";
@@ -6167,7 +6275,7 @@ class DegradedPassRefusalError extends Error {
6167
6275
  estimatedTokens;
6168
6276
  contextLimitTokens;
6169
6277
  constructor(site, options) {
6170
- super(renderUserFacingFailure("transform_pass_degraded", "plain"), {
6278
+ super(describeDegradedPassFailure(site, options?.cause), {
6171
6279
  cause: options?.cause
6172
6280
  });
6173
6281
  this.site = site;
@@ -8776,7 +8884,7 @@ function maybeInjectChannel1Nudge(args, sessionId, tool, output) {
8776
8884
  setChannel1NudgeState(args.db, sessionId, nextNudgeState);
8777
8885
  return;
8778
8886
  }
8779
- out.output += buildChannel1Reminder(decision.level, decision.undroppedTokens, reclaimableToolOutputCount(state.baselineParts), state.oldestReclaimableToolTags, decision.sticky);
8887
+ out.output += buildChannel1Reminder(decision.level, decision.undroppedTokens, currentReclaimableToolOutputCount(state), state.oldestReclaimableToolTags, decision.sticky);
8780
8888
  setChannel1NudgeState(args.db, sessionId, {
8781
8889
  ...nextNudgeState,
8782
8890
  ordinal: state.realUserTurnCount
@@ -10008,9 +10116,6 @@ class StorageBusyRefusalError extends Error {
10008
10116
  }
10009
10117
  }
10010
10118
 
10011
- // src/hooks/magic-context/system-prompt-hash.ts
10012
- import { createHash as createHash3 } from "node:crypto";
10013
-
10014
10119
  // src/agents/magic-context-prompt.ts
10015
10120
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
10016
10121
 
@@ -10152,6 +10257,90 @@ ${BASE_INTRO(memoryEnabled, temporalAwarenessEnabled)}${cavemanWarning}
10152
10257
  ${SELF_TAG_GUIDANCE}${languageGuidance}`;
10153
10258
  }
10154
10259
 
10260
+ // src/features/magic-context/transform-decision-log.ts
10261
+ var canonicalReasons = new Set([
10262
+ "system_hash",
10263
+ "model_change",
10264
+ "ttl_idle",
10265
+ "explicit_flush",
10266
+ "max_mutation_id",
10267
+ "first_render",
10268
+ "pressure_refold",
10269
+ "upgrade_state",
10270
+ "cached_m1_missing",
10271
+ "project_change",
10272
+ "compartment_render_epoch",
10273
+ "m1_delta",
10274
+ "ttl_expiry",
10275
+ "epoch_change",
10276
+ "coverage_fold",
10277
+ "profile_transition",
10278
+ "host_compaction"
10279
+ ]);
10280
+ var piReasonAliases = {
10281
+ pending_mutations: "max_mutation_id",
10282
+ renderer_upgrade: "upgrade_state",
10283
+ cache_invalid: "cached_m1_missing",
10284
+ drift: "pressure_refold"
10285
+ };
10286
+ var sharedReasonAliases = {
10287
+ model_key: "model_change",
10288
+ pressure: "pressure_refold"
10289
+ };
10290
+ var pendingDecisionBySession = new Map;
10291
+ var pendingPiDecisionBySession = new Map;
10292
+ var lastBoundMessageIdBySession = new Map;
10293
+ var scheduledWriteTokensBySession = new Map;
10294
+ var systemHashChangedSessions = new Set;
10295
+ var piSystemHashChangedSessions = new Set;
10296
+ var unbustedPassBySession = new Map;
10297
+ var newestInputAssistantBySession = new Map;
10298
+ var stagedV2DecisionBySession = new Map;
10299
+ function normalizeMaterializeReason(harness, reason, rematerialized) {
10300
+ const raw = typeof reason === "string" ? reason.trim() : "";
10301
+ if (raw.length > 0) {
10302
+ const alias = sharedReasonAliases[raw] ?? (harness === "pi" ? piReasonAliases[raw] : undefined) ?? undefined;
10303
+ if (alias)
10304
+ return alias;
10305
+ if (canonicalReasons.has(raw))
10306
+ return raw;
10307
+ return null;
10308
+ }
10309
+ return rematerialized ? "pressure_refold" : null;
10310
+ }
10311
+ function clearOpenCodePendingTransformDecision(sessionId, inputMessages) {
10312
+ pendingDecisionBySession.delete(sessionId);
10313
+ unbustedPassBySession.delete(sessionId);
10314
+ if (!inputMessages)
10315
+ return;
10316
+ let newestAssistant = "";
10317
+ for (const message of inputMessages) {
10318
+ const id = message.info?.id;
10319
+ if (message.info?.role !== "assistant" || typeof id !== "string")
10320
+ continue;
10321
+ if (id > newestAssistant)
10322
+ newestAssistant = id;
10323
+ }
10324
+ newestInputAssistantBySession.set(sessionId, newestAssistant);
10325
+ }
10326
+ function noteSystemPromptHashChange(sessionId) {
10327
+ systemHashChangedSessions.add(sessionId);
10328
+ }
10329
+ function noteOpenCodeUnbustedPass(sessionId, decision) {
10330
+ unbustedPassBySession.set(sessionId, decision);
10331
+ }
10332
+ var IDLE_EXPIRY_REASONS = new Set([
10333
+ "ttl_idle",
10334
+ "ttl_expiry"
10335
+ ]);
10336
+ function recordPendingTransformDecision(sessionId, decision) {
10337
+ if (!decision.bustedThisPass) {
10338
+ pendingDecisionBySession.delete(sessionId);
10339
+ return;
10340
+ }
10341
+ pendingDecisionBySession.set(sessionId, decision);
10342
+ }
10343
+
10155
10344
  // src/hooks/magic-context/system-prompt-dates.ts
10156
10345
  function replaceHostDates(prompt, replace) {
10157
10346
  const fields = /<(plugin-env|environment|env)>[\s\S]*?<\/\1>|Today's date: [^\r\n<]+/g;
@@ -10398,7 +10587,7 @@ function createSystemPromptHashHandler(deps) {
10398
10587
  const dates = systemPromptDates(liveSystemContent, stickyDate);
10399
10588
  const currentDate = dates.live;
10400
10589
  const stableCandidate = dates.stable;
10401
- const stableCandidateHash = createHash3("md5").update(promptSurfaceHashMaterial(stableCandidate, promptSurface.preset)).digest("hex");
10590
+ const stableCandidateHash = systemPromptFingerprint(stableCandidate, promptSurface.preset);
10402
10591
  const contentOrPresetChanged = hasPersistedHash && stableCandidateHash !== previousHash;
10403
10592
  const rewritePermission = systemDateRefreshPermission(deps.historyRefreshSessions, dateRequest);
10404
10593
  let deferredDateRefresh = false;
@@ -10412,7 +10601,7 @@ function createSystemPromptHashHandler(deps) {
10412
10601
  try {
10413
10602
  const content = liveElements.join(`
10414
10603
  `);
10415
- const hash = createHash3("md5").update(promptSurfaceHashMaterial(content, promptSurface.preset)).digest("hex");
10604
+ const hash = systemPromptFingerprint(content, promptSurface.preset);
10416
10605
  updateSessionMeta(deps.db, sessionId, {
10417
10606
  systemPromptHash: hash,
10418
10607
  cachedM0SystemHash: hash,
@@ -10452,7 +10641,7 @@ function createSystemPromptHashHandler(deps) {
10452
10641
  `);
10453
10642
  if (!availability.frozen || !modelKey)
10454
10643
  return;
10455
- const currentHash = createHash3("md5").update(promptSurfaceHashMaterial(systemContent, promptSurface.preset)).digest("hex");
10644
+ const currentHash = systemPromptFingerprint(systemContent, promptSurface.preset);
10456
10645
  recordSystemPromptForRequest(sessionId, currentLkgRequestIdentity(sessionId), currentHash, deps.db, previousHash !== currentHash ? !deps.historyRefreshSessions.has(sessionId) : undefined, deps.historyRefreshSessions, deps.pendingMaterializationSessions);
10457
10646
  if (!sessionMetaEarly) {
10458
10647
  return;
@@ -10460,6 +10649,7 @@ function createSystemPromptHashHandler(deps) {
10460
10649
  const sessionMeta = sessionMetaEarly;
10461
10650
  if (previousHash !== "" && previousHash !== "0" && previousHash !== currentHash) {
10462
10651
  sessionLog(sessionId, `system prompt hash changed: ${previousHash} → ${currentHash} (len=${systemContent.length}), ${idleCacheExpired ? "adopting on idle-expired request" : requestMessagesFinal ? "adopting on the request whose messages are already final" : "triggering flush"}`);
10652
+ noteSystemPromptHashChange(sessionId);
10463
10653
  if (!adoptSystemChange) {
10464
10654
  deps.historyRefreshSessions.add(sessionId);
10465
10655
  deps.systemPromptRefreshSessions.add(sessionId);
@@ -10520,10 +10710,16 @@ class MaintenanceLauncher {
10520
10710
  constructor(deps) {
10521
10711
  this.deps = deps;
10522
10712
  }
10713
+ modelConfigured() {
10714
+ return hasMaintenancePrimary(this.deps.getConfig().maintenance);
10715
+ }
10523
10716
  maybeLaunch(signals) {
10524
10717
  if (!this.deps.getConfig().maintenance.enabled) {
10525
10718
  return { launched: false, reason: "maintenance-disabled" };
10526
10719
  }
10720
+ if (!this.modelConfigured()) {
10721
+ return { launched: false, reason: "maintenance-model-unconfigured" };
10722
+ }
10527
10723
  if (this.runInFlight) {
10528
10724
  return { launched: false, reason: "run-in-flight-local" };
10529
10725
  }
@@ -10553,6 +10749,9 @@ class MaintenanceLauncher {
10553
10749
  if (!this.deps.getConfig().maintenance.enabled) {
10554
10750
  return { launched: false, reason: "maintenance-disabled" };
10555
10751
  }
10752
+ if (!this.modelConfigured()) {
10753
+ return { launched: false, reason: "maintenance-model-unconfigured" };
10754
+ }
10556
10755
  if (this.runInFlight) {
10557
10756
  return { launched: false, reason: "run-in-flight-local" };
10558
10757
  }
@@ -10692,63 +10891,6 @@ function hasRecordedSessionProjectIdentity(db, sessionId) {
10692
10891
  return row?.found === 1;
10693
10892
  }
10694
10893
 
10695
- // src/features/magic-context/transform-decision-log.ts
10696
- var canonicalReasons = new Set([
10697
- "system_hash",
10698
- "model_change",
10699
- "ttl_idle",
10700
- "explicit_flush",
10701
- "max_mutation_id",
10702
- "first_render",
10703
- "pressure_refold",
10704
- "upgrade_state",
10705
- "cached_m1_missing",
10706
- "project_change",
10707
- "compartment_render_epoch",
10708
- "m1_delta",
10709
- "ttl_expiry",
10710
- "epoch_change",
10711
- "coverage_fold",
10712
- "profile_transition",
10713
- "host_compaction"
10714
- ]);
10715
- var piReasonAliases = {
10716
- pending_mutations: "max_mutation_id",
10717
- renderer_upgrade: "upgrade_state",
10718
- cache_invalid: "cached_m1_missing",
10719
- drift: "pressure_refold"
10720
- };
10721
- var sharedReasonAliases = {
10722
- model_key: "model_change",
10723
- pressure: "pressure_refold"
10724
- };
10725
- var pendingDecisionBySession = new Map;
10726
- var pendingPiDecisionBySession = new Map;
10727
- var lastBoundMessageIdBySession = new Map;
10728
- var scheduledWriteTokensBySession = new Map;
10729
- function normalizeMaterializeReason(harness, reason, rematerialized) {
10730
- const raw = typeof reason === "string" ? reason.trim() : "";
10731
- if (raw.length > 0) {
10732
- const alias = sharedReasonAliases[raw] ?? (harness === "pi" ? piReasonAliases[raw] : undefined) ?? undefined;
10733
- if (alias)
10734
- return alias;
10735
- if (canonicalReasons.has(raw))
10736
- return raw;
10737
- return null;
10738
- }
10739
- return rematerialized ? "pressure_refold" : null;
10740
- }
10741
- function clearOpenCodePendingTransformDecision(sessionId) {
10742
- pendingDecisionBySession.delete(sessionId);
10743
- }
10744
- function recordPendingTransformDecision(sessionId, decision) {
10745
- if (!decision.bustedThisPass) {
10746
- pendingDecisionBySession.delete(sessionId);
10747
- return;
10748
- }
10749
- pendingDecisionBySession.set(sessionId, decision);
10750
- }
10751
-
10752
10894
  // src/hooks/magic-context/cache-busting-signals.ts
10753
10895
  function canConsumeDeferredOnThisPass(args) {
10754
10896
  if (args.justAwaitedPublication)
@@ -11422,12 +11564,12 @@ function buildTriggerInMemoryTail(db, sessionId, messages) {
11422
11564
  function getProactiveCompartmentTriggerPercentage(executeThresholdPercentage) {
11423
11565
  return Math.max(0, executeThresholdPercentage - PROACTIVE_TRIGGER_OFFSET_PERCENTAGE);
11424
11566
  }
11425
- function estimateProjectedPostDropPercentage(db, sessionId, usage, activeTags, keepReasoningTokens, clearedReasoningThroughTag, canClearReasoning, liveBudgetCutoff) {
11567
+ function estimateProjectedPostDropPercentage(db, sessionId, usage, activeTags, keepReasoningTokens, clearedReasoningThroughTag, canClearReasoning, liveBudgetCutoff, eligiblePendingDropTagIds) {
11426
11568
  const totalActiveBytes = activeTags.reduce((sum, tag) => sum + tag.byteSize + tag.reasoningByteSize, 0);
11427
11569
  if (totalActiveBytes === 0)
11428
11570
  return null;
11429
11571
  let droppableBytes = 0;
11430
- const pendingDrops = getPendingOps(db, sessionId).filter((op) => op.operation === "drop");
11572
+ const pendingDrops = getPendingOps(db, sessionId).filter((op) => op.operation === "drop" && eligiblePendingDropTagIds?.has(op.tagId) === true);
11431
11573
  const pendingDropTagIds = new Set(pendingDrops.map((op) => op.tagId));
11432
11574
  if (pendingDrops.length > 0) {
11433
11575
  droppableBytes += activeTags.filter((tag) => pendingDropTagIds.has(tag.tagNumber)).reduce((sum, tag) => sum + tag.byteSize + tag.reasoningByteSize, 0);
@@ -11555,7 +11697,7 @@ function getUnsummarizedTailInfo(db, sessionId, triggerBudget, usage, executeThr
11555
11697
  function formatProjectedPostDropPercentage(value) {
11556
11698
  return value === null ? "none" : `${value.toFixed(1)}%`;
11557
11699
  }
11558
- function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPercentage, executeThresholdPercentage, triggerBudget, keepReasoningTokens, commitClusterTrigger, preloadedActiveTags, contextLimit, inMemoryTail, taggerFloorOverride, reasoningProjection, reclaimRide, storedTotalsExcludeToolTags) {
11700
+ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPercentage, executeThresholdPercentage, triggerBudget, keepReasoningTokens, commitClusterTrigger, preloadedActiveTags, contextLimit, inMemoryTail, taggerFloorOverride, reasoningProjection, reclaimRide, eligiblePendingDropTagIds, storedTotalsExcludeToolTags) {
11559
11701
  if (sessionMeta.compartmentInProgress) {
11560
11702
  sessionLog(sessionId, `compartment trigger: skipped — historian already in progress (usage=${usage.percentage.toFixed(1)}%)`);
11561
11703
  return { shouldFire: false };
@@ -11619,7 +11761,7 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
11619
11761
  return { shouldFire: false };
11620
11762
  }
11621
11763
  const canClearReasoning = reasoningProjection?.canClearReasoning ?? modelAcceptsEmptyContent(reasoningProjection?.providerID);
11622
- const projectedPostDropPercentage = estimateProjectedPostDropPercentage(db, sessionId, usage, preloadedActiveTags ?? getActiveTagsBySession(db, sessionId), keepReasoningTokens, sessionMeta.clearedReasoningThroughTag, canClearReasoning, reasoningProjection?.budgetCutoff);
11764
+ const projectedPostDropPercentage = estimateProjectedPostDropPercentage(db, sessionId, usage, preloadedActiveTags ?? getActiveTagsBySession(db, sessionId), keepReasoningTokens, sessionMeta.clearedReasoningThroughTag, canClearReasoning, reasoningProjection?.budgetCutoff, eligiblePendingDropTagIds);
11623
11765
  const relativePostDropTarget = executeThresholdPercentage * POST_DROP_TARGET_RATIO;
11624
11766
  const rideLabel = reclaimRide ? reclaimRideLabel(reclaimRide) : "ride=none";
11625
11767
  const dropsCanLand = reclaimRide !== undefined && hasReclaimRide(reclaimRide);
@@ -11703,6 +11845,12 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
11703
11845
  };
11704
11846
  }
11705
11847
 
11848
+ // src/hooks/magic-context/compartment-trigger-error.ts
11849
+ function recordCompartmentTriggerError(passOutcome, sessionId, error, log = sessionLog) {
11850
+ passOutcome.record("compartment-trigger-failure");
11851
+ log(sessionId, "compartment trigger failed (non-fatal):", error);
11852
+ }
11853
+
11706
11854
  // src/hooks/magic-context/pass-outcome.ts
11707
11855
  var PASS_DEGRADATION_EFFECTS = {
11708
11856
  "store-generation-rebase-failure": "changes-request",
@@ -11763,6 +11911,37 @@ function createPassOutcome() {
11763
11911
  };
11764
11912
  }
11765
11913
 
11914
+ // src/hooks/magic-context/render-options.ts
11915
+ function createM0RenderOptions(controls, contextUsage, modelKey, resolvedContextLimit, projectPath) {
11916
+ return {
11917
+ projectPath: controls.memoryConfig?.enabled ? projectPath : undefined,
11918
+ memoryEnabled: controls.memoryConfig?.enabled,
11919
+ memoryInjectionBudgetTokens: controls.memoryConfig?.injectionBudgetTokens,
11920
+ temporalAwareness: controls.experimentalTemporalAwareness,
11921
+ historyBudgetTokens: resolveHistoryBudgetTokens(controls.historyBudgetPercentage, contextUsage, controls.executeThresholdPercentage, modelKey, controls.executeThresholdTokens, resolvedContextLimit),
11922
+ historyBudgetPolicyIdentity: historyBudgetPolicyIdentity(controls.historyBudgetPercentage, controls.executeThresholdPercentage, modelKey, controls.executeThresholdTokens)
11923
+ };
11924
+ }
11925
+ function resolveHistoryBudgetTokens(historyBudgetPercentage, contextUsage, executeThresholdPercentage, modelKey, executeThresholdTokens, resolvedContextLimit) {
11926
+ if (!historyBudgetPercentage) {
11927
+ return;
11928
+ }
11929
+ let contextLimit = resolvedContextLimit && resolvedContextLimit > 0 ? resolvedContextLimit : 0;
11930
+ if (contextLimit <= 0) {
11931
+ if (contextUsage.percentage <= 0) {
11932
+ return;
11933
+ }
11934
+ contextLimit = contextUsage.inputTokens / (contextUsage.percentage / 100);
11935
+ }
11936
+ if (!Number.isFinite(contextLimit) || contextLimit <= 0) {
11937
+ return;
11938
+ }
11939
+ return Math.floor(contextLimit * (resolveExecuteThreshold(executeThresholdPercentage ?? 65, modelKey, 65, {
11940
+ tokensConfig: executeThresholdTokens,
11941
+ contextLimit
11942
+ }) / 100) * historyBudgetPercentage);
11943
+ }
11944
+
11766
11945
  // src/hooks/magic-context/temporal-served-projection.ts
11767
11946
  function readLkgTemporalDecisions(db, sessionId, runtime) {
11768
11947
  const slot = getInMemorySlot(sessionId) ?? loadPersistedLkgSlot(db, sessionId);
@@ -11955,6 +12134,13 @@ async function runCompartmentPhaseImpl(args) {
11955
12134
  return snapshot !== null && hasRunnableCompartmentWindow(snapshot);
11956
12135
  }
11957
12136
  async function awaitCompartmentRun(activeRun, reason) {
12137
+ await Promise.resolve();
12138
+ if (activeRun.inlineJoinTimedOutBudgetMs !== undefined && !activeRun.settled) {
12139
+ historianJoinTimedOut = true;
12140
+ historianJoinBudgetMs = activeRun.inlineJoinTimedOutBudgetMs;
12141
+ sessionLog(args.sessionId, "transform: compartment inline join budget already exhausted for this run — not waiting again");
12142
+ return "timed_out";
12143
+ }
11958
12144
  sessionLog(args.sessionId, reason);
11959
12145
  const timeoutMs = resolveHistorianInlineJoinBudget(args.historianTimeoutMs);
11960
12146
  historianJoinBudgetMs = timeoutMs;
@@ -11970,6 +12156,7 @@ async function runCompartmentPhaseImpl(args) {
11970
12156
  clearTimeout(timer);
11971
12157
  }
11972
12158
  if (result === "timeout") {
12159
+ activeRun.inlineJoinTimedOutBudgetMs = timeoutMs;
11973
12160
  historianJoinTimedOut = true;
11974
12161
  sessionLog(args.sessionId, `transform: compartment await timed out after ${timeoutMs}ms — proceeding without waiting`);
11975
12162
  return "timed_out";
@@ -12177,6 +12364,76 @@ function resolveSchedulerDecision(scheduler, sessionMeta, contextUsage, sessionI
12177
12364
  }
12178
12365
  }
12179
12366
 
12367
+ // src/hooks/magic-context/pending-drop-eligibility.ts
12368
+ function pendingDropVeto(tagNumber, target, protectedTagIds) {
12369
+ if (protectedTagIds.has(tagNumber))
12370
+ return "protected";
12371
+ if (target?.thinkingDropProtected)
12372
+ return "active_thinking";
12373
+ return null;
12374
+ }
12375
+ function capturePendingDropEligibility(targets, protectedTagIds) {
12376
+ const eligible = new Set;
12377
+ for (const [number, target] of targets) {
12378
+ if (pendingDropVeto(number, target, protectedTagIds))
12379
+ continue;
12380
+ if (target.drop) {
12381
+ if (target.canDrop?.() === true)
12382
+ eligible.add(number);
12383
+ } else {
12384
+ const content = target.getContent?.();
12385
+ if (typeof content === "string" && content.length > `[dropped §${number}§]`.length)
12386
+ eligible.add(number);
12387
+ }
12388
+ }
12389
+ return eligible;
12390
+ }
12391
+ function pendingDropEligibilityKey(db, sessionId, input, protectionInputs) {
12392
+ const digests = input.map((message) => message.contentDigest?.() ?? null);
12393
+ if (digests.some((digest) => digest === null))
12394
+ return null;
12395
+ const revision = db.prepare("SELECT value FROM schema_migrations_meta WHERE key = ?").get(tagRevisionKey(sessionId));
12396
+ if (!revision?.value)
12397
+ return null;
12398
+ return JSON.stringify([
12399
+ input.map((message, index) => [
12400
+ message.id,
12401
+ message.role,
12402
+ message.synthetic,
12403
+ digests[index]
12404
+ ]),
12405
+ revision.value,
12406
+ readEpochFloorSnapshot(db, sessionId),
12407
+ sessionDecisionCalibration(db, sessionId).toolsRatio,
12408
+ loadProtectedTailMeta(db, sessionId),
12409
+ [...getMergedReasoningStrippedIds(db, sessionId)].sort(),
12410
+ [...getReasoningRemovalState(db, sessionId).messageIds].sort(),
12411
+ protectionInputs
12412
+ ]);
12413
+ }
12414
+ function createPendingDropEligibilityCache() {
12415
+ const sessions = new Map;
12416
+ return {
12417
+ read(sessionId, key) {
12418
+ const previous = sessions.get(sessionId);
12419
+ if (key === null || previous?.key !== key) {
12420
+ sessions.delete(sessionId);
12421
+ return;
12422
+ }
12423
+ return previous.eligible;
12424
+ },
12425
+ record(sessionId, key, eligible) {
12426
+ sessions.delete(sessionId);
12427
+ if (key === null)
12428
+ return;
12429
+ const oldest = sessions.keys().next().value;
12430
+ if (sessions.size >= 64 && oldest !== undefined)
12431
+ sessions.delete(oldest);
12432
+ sessions.set(sessionId, { key, eligible: new Set(eligible) });
12433
+ }
12434
+ };
12435
+ }
12436
+
12180
12437
  // src/hooks/magic-context/apply-operations.ts
12181
12438
  var RECENT_TOOL_SKELETON_WINDOW = 20;
12182
12439
  function buildReplacementContent(tagId) {
@@ -12285,7 +12542,7 @@ function renderConvertedToolSkeletons(targets, converted) {
12285
12542
  }
12286
12543
  return didMutate;
12287
12544
  }
12288
- function applyPendingOperations(sessionId, db, targets, protectedTagIds, preloadedTags, preloadedPendingOps, syntheticPendingOps = [], editMarkerTagIds = new Set, onTagReduced, onBatchComplete) {
12545
+ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preloadedTags, preloadedPendingOps, syntheticPendingOps = [], editMarkerTagIds = new Set, onTagReduced, onBatchComplete, onEligibilitySnapshot) {
12289
12546
  let didMutateMessage = false;
12290
12547
  let diagnostics;
12291
12548
  const reject = (reason) => {
@@ -12300,6 +12557,7 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
12300
12557
  }).immediate();
12301
12558
  {
12302
12559
  const pendingOps = preloadedPendingOps ?? getPendingOps(db, sessionId);
12560
+ onEligibilitySnapshot?.(capturePendingDropEligibility(targets, protectedTagIds));
12303
12561
  const neededTagNumbers = new Set(pendingOps.map((op) => op.tagId));
12304
12562
  for (const op of syntheticPendingOps)
12305
12563
  neededTagNumbers.add(op.tagId);
@@ -12336,13 +12594,10 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
12336
12594
  removePendingOp(db, sessionId, pendingOp.tagId);
12337
12595
  continue;
12338
12596
  }
12339
- if (protectedTagIds.has(pendingOp.tagId)) {
12340
- reject("protected");
12341
- continue;
12342
- }
12343
12597
  const target = targets.get(pendingOp.tagId);
12344
- if (target?.thinkingDropProtected) {
12345
- reject("active_thinking");
12598
+ const veto = pendingDropVeto(pendingOp.tagId, target, protectedTagIds);
12599
+ if (veto) {
12600
+ reject(veto);
12346
12601
  continue;
12347
12602
  }
12348
12603
  const isToolTag = tagTypeById.get(pendingOp.tagId) === "tool";
@@ -12362,9 +12617,7 @@ function applyPendingOperations(sessionId, db, targets, protectedTagIds, preload
12362
12617
  try {
12363
12618
  db.transaction(() => {
12364
12619
  startedAt = performance.now();
12365
- if (mode)
12366
- updateTagDropMode(db, sessionId, pendingOp.tagId, mode);
12367
- updateTagStatus(db, sessionId, pendingOp.tagId, "dropped");
12620
+ markTagDropped(db, sessionId, pendingOp.tagId, mode);
12368
12621
  if (!synthetic)
12369
12622
  removePendingOp(db, sessionId, pendingOp.tagId);
12370
12623
  }).immediate();
@@ -13774,7 +14027,7 @@ function buildEditSupersessionReclaim(input) {
13774
14027
  seenFile.add(filePath);
13775
14028
  continue;
13776
14029
  }
13777
- if (input.recentMessageIds && (tag.toolOwnerMessageId === null || input.recentMessageIds.has(tag.toolOwnerMessageId))) {
14030
+ if (tag.tagNumber > input.watermark || input.recentMessageIds && (tag.toolOwnerMessageId === null || input.recentMessageIds.has(tag.toolOwnerMessageId))) {
13778
14031
  continue;
13779
14032
  }
13780
14033
  if (realPendingTagIds.has(tag.tagNumber))
@@ -13822,6 +14075,51 @@ async function waitForTagOwners(db, sessionId, ratio, _waitMs = 250) {
13822
14075
  return prewarmTagOwners(db, sessionId, ratio);
13823
14076
  }
13824
14077
 
14078
+ // src/shared/reclaim-distance.ts
14079
+ var RECLAIM_DISTANCE_USER_TURNS = 3;
14080
+ var RECLAIM_DISTANCE_WINDOW_FRACTION = 0.25;
14081
+ function isReclaimUserTurn(args) {
14082
+ return args.role === "user" && !args.syntheticHead && !args.synthetic && !args.ignored && args.parts.some((part) => {
14083
+ if (part.synthetic || part.ignored)
14084
+ return false;
14085
+ if (part.kind !== "text" && part.kind !== "image" && part.kind !== "file")
14086
+ return false;
14087
+ if (part.kind !== "text")
14088
+ return true;
14089
+ return stripReclaimMcText(part.text ?? "").trim().length > 0;
14090
+ });
14091
+ }
14092
+ function stripReclaimMcText(text) {
14093
+ return text.replace(/^§\d+§\s*/, "").replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, (reminder) => /^(?:Your next step: call ctx_reduce on the outputs you've already used|Still unstamped:|Housekeeping: .* are reclaimable\. Stamp each output with ctx_reduce|.* are reclaimable\. Make a ctx_reduce pass now|.* are still unstamped\. Call ctx_reduce now)/.test(reminder.slice("<system-reminder>".length).trimStart()) ? "" : reminder).replace(/<instruction name="deferred_notes">[\s\S]*?<\/instruction>/g, "");
14094
+ }
14095
+ function farBehindToolBookmark(entries, usableWindow) {
14096
+ let newerTurns = 0;
14097
+ let newerTokens = 0;
14098
+ const farTags = [];
14099
+ let firstYoungTag = Infinity;
14100
+ const seenOutputs = new Set;
14101
+ const tokenDistance = usableWindow > 0 && Number.isFinite(usableWindow) ? usableWindow * RECLAIM_DISTANCE_WINDOW_FRACTION : Infinity;
14102
+ for (let index = entries.length - 1;index >= 0; index--) {
14103
+ const entry = entries[index];
14104
+ if (!entry)
14105
+ continue;
14106
+ const tag = entry.toolOutputTag;
14107
+ if (tag !== undefined && !seenOutputs.has(tag)) {
14108
+ seenOutputs.add(tag);
14109
+ if (newerTurns >= RECLAIM_DISTANCE_USER_TURNS || newerTokens >= tokenDistance) {
14110
+ farTags.push(tag);
14111
+ } else {
14112
+ firstYoungTag = Math.min(firstYoungTag, tag);
14113
+ }
14114
+ }
14115
+ if (entry.userTurn)
14116
+ newerTurns++;
14117
+ if (Number.isFinite(entry.tokens) && entry.tokens > 0)
14118
+ newerTokens += entry.tokens;
14119
+ }
14120
+ return farTags.reduce((bookmark, tag) => tag < firstYoungTag ? Math.max(bookmark, tag) : bookmark, 0);
14121
+ }
14122
+
13825
14123
  // src/hooks/magic-context/tool-reclaim.ts
13826
14124
  function buildSyntheticToolReclaimOps(input) {
13827
14125
  const watermark = Math.max(0, input.watermark);
@@ -13853,10 +14151,74 @@ function buildSyntheticToolReclaimOps(input) {
13853
14151
  }
13854
14152
  return synthetic;
13855
14153
  }
13856
- function advanceToolReclaimWatermarkToCurrentMax(db, sessionId) {
13857
- const maxTagNumber = getMaxTagNumberBySession(db, sessionId);
13858
- advanceToolReclaimWatermark(db, sessionId, maxTagNumber);
13859
- return maxTagNumber;
14154
+ function advanceToolReclaimWatermarkByDistance(db, sessionId, entries, usableWindow) {
14155
+ const bookmark = farBehindToolBookmark(entries, usableWindow);
14156
+ advanceToolReclaimWatermark(db, sessionId, bookmark);
14157
+ return bookmark;
14158
+ }
14159
+ function toolReclaimDistanceEntries(messages, targets) {
14160
+ const outputTags = new Map;
14161
+ for (const [tag, target] of targets) {
14162
+ for (const location of target.mutationParts ?? []) {
14163
+ const part = distancePart(location.part);
14164
+ if (part.kind === "tool_result" || part.kind === "tool")
14165
+ outputTags.set(location.part, tag);
14166
+ }
14167
+ }
14168
+ const entries = [];
14169
+ for (const message of messages) {
14170
+ const parts = message.parts.map(distancePart);
14171
+ let userTurn = isReclaimUserTurn({ ...message.info, parts });
14172
+ for (const [index, part] of parts.entries()) {
14173
+ const authored = isReclaimUserTurn({ role: "user", parts: [part] });
14174
+ entries.push({
14175
+ tokens: message.info.syntheticHead ? 0 : estimateTokens(stripReclaimMcText(part.text ?? "")) + part.imageTokens,
14176
+ userTurn: userTurn && authored,
14177
+ toolOutputTag: outputTags.get(message.parts[index])
14178
+ });
14179
+ if (authored)
14180
+ userTurn = false;
14181
+ }
14182
+ }
14183
+ return entries;
14184
+ }
14185
+ function distancePart(raw) {
14186
+ if (!raw || typeof raw !== "object")
14187
+ return { kind: "structural", imageTokens: 0 };
14188
+ const part = raw;
14189
+ const provenance = { synthetic: part.synthetic === true, ignored: part.ignored === true };
14190
+ if (typeof part.getText === "function") {
14191
+ const text = part.getText();
14192
+ return {
14193
+ ...provenance,
14194
+ kind: String(part.kind),
14195
+ text: typeof text === "string" ? text : undefined,
14196
+ imageTokens: typeof part.imageTokens === "function" ? Number(part.imageTokens()) : 0
14197
+ };
14198
+ }
14199
+ const metadata = { ...provenance, imageTokens: estimatePartImageTokens(raw) };
14200
+ if (part.type === "tool") {
14201
+ const state = part.state;
14202
+ return {
14203
+ ...metadata,
14204
+ kind: "tool",
14205
+ text: `${JSON.stringify(state?.input ?? {})}
14206
+ ${typeof state?.output === "string" ? state.output : JSON.stringify(state?.output ?? "")}`
14207
+ };
14208
+ }
14209
+ if (part.type === "tool_use")
14210
+ return { ...metadata, kind: "tool_use", text: JSON.stringify(part.input ?? {}) };
14211
+ if (part.type === "tool_result")
14212
+ return {
14213
+ ...metadata,
14214
+ kind: "tool_result",
14215
+ text: typeof part.content === "string" ? part.content : JSON.stringify(part.content ?? "")
14216
+ };
14217
+ return {
14218
+ ...metadata,
14219
+ kind: String(part.type),
14220
+ text: typeof part.text === "string" ? part.text : typeof part.thinking === "string" ? part.thinking : undefined
14221
+ };
13860
14222
  }
13861
14223
 
13862
14224
  // src/hooks/magic-context/transform-postprocess-phase.ts
@@ -14187,6 +14549,25 @@ function finalizeMessageRepresentation(messages, resolvedProviderID, options) {
14187
14549
  }
14188
14550
  return { clearedParts, mergedReasoningParts };
14189
14551
  }
14552
+ function legacyPublishedHeadChanged(args) {
14553
+ if (!args.pendingCompartmentInjection || !args.readLastServedMessages)
14554
+ return false;
14555
+ const served = args.readLastServedMessages();
14556
+ if (served === null)
14557
+ return true;
14558
+ const previous = served[0];
14559
+ if (!Array.isArray(previous?.parts) || !args.pendingCompartmentInjection)
14560
+ return false;
14561
+ const text = previous.parts.find((part) => typeof part === "object" && part !== null && ("type" in part) && part.type === "text" && ("text" in part))?.text;
14562
+ if (typeof text !== "string")
14563
+ return false;
14564
+ const block = `<session-history>
14565
+ ${args.pendingCompartmentInjection.block}
14566
+ </session-history>`;
14567
+ return text !== block && !text.startsWith(`${block}
14568
+
14569
+ `);
14570
+ }
14190
14571
  async function runPostTransformPhase(args) {
14191
14572
  const tPostprocessSetup = performance.now();
14192
14573
  const compactionOff = args.compactionOff === true;
@@ -14390,11 +14771,6 @@ async function runPostTransformPhase(args) {
14390
14771
  }
14391
14772
  sessionLog(args.sessionId, `m[0] HARD fold decision: reason=${m0MaterializeReason ?? foldDueDecision.reason ?? (foldExecutedThisPass ? "drift" : "soft_refresh")} executed=${foldExecutedThisPass} bustsServedPrefix=${foldBustsServedPrefixThisPass}`);
14392
14773
  }
14393
- if (args.normalizeRecoveryTail && (foldBustsServedPrefixThisPass || firstRenderBust)) {
14394
- args.normalizeRecoveryTail();
14395
- args.tags = getTagsBySession(args.db, args.sessionId);
14396
- newTargets = protectNewTagMutations(args.messages, args.targets, retainedThinking, args.thinkingBindingRecoveryEnabledForModel === true);
14397
- }
14398
14774
  logTransformTiming(args.sessionId, "pp.prefixPreflight", tPostprocessSetup, `foldExecuted=${foldExecutedThisPass} m1Refreshed=${publishedM1RefreshedThisPass}`);
14399
14775
  const tPassDecisions = performance.now();
14400
14776
  const frontToolCandidates = args.tags.filter((tag) => tag.type === "tool" && tag.status === "active" && args.targets.get(tag.tagNumber)?.canDrop?.());
@@ -14421,13 +14797,19 @@ async function runPostTransformPhase(args) {
14421
14797
  subagentExecute: !args.fullFeatureMode && args.schedulerDecision === "execute",
14422
14798
  hardFold: foldBustsServedPrefixThisPass || firstRenderBust || idleExpiryRebuild,
14423
14799
  force: emergencyDropEligible && (args.contextUsage.percentage >= 95 || getEmergencyInputSample(args.db, args.sessionId) === 0),
14424
- explicitFlush: isExplicitFlush || deferredMaterialize && !prefixPreflightFailed,
14425
- publishedHistory: publishedM1RefreshedThisPass || !m0M1EnabledForFold && (args.pendingCompartmentInjection?.compartmentCount ?? 0) > 0 && (args.historyRebuiltThisPass || args.rebuiltHistoryFromInitialPrepare || args.canConsumeDeferredLate && args.deferredHistoryWasPendingAtPassStart)
14800
+ explicitFlush: isExplicitFlush && hasPendingMaterializationOrigin(args.pendingMaterializationSessions, args.sessionId, "user_flush"),
14801
+ publishedHistory: !prefixPreflightFailed && (publishedM1RefreshedThisPass || !m0M1EnabledForFold && (args.pendingCompartmentInjection?.compartmentCount ?? 0) > 0 && legacyPublishedHeadChanged(args))
14426
14802
  };
14427
14803
  const publishedWorkDrainAllowed = !compactionOff && (!activeThinkingPrefixHold || firstRenderBust || idleExpiryRebuild || emergencyDropEligible) && hasReclaimRide(rideSignals);
14428
14804
  const shouldApplyPendingOps = publishedWorkDrainAllowed;
14429
14805
  const shouldRunHeuristics = !compactionOff && publishedWorkDrainAllowed && (rideSignals.publishedHistory || materializationRequested || forceMaterialization || foldBustsServedPrefixThisPass || firstRenderBust || heldIdleExpiryRebuild || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
14430
14806
  const isCacheBustingPass = publishedWorkDrainAllowed;
14807
+ if (args.normalizeRecoveryTail && isCacheBustingPass) {
14808
+ args.normalizeRecoveryTail();
14809
+ args.tags = getTagsBySession(args.db, args.sessionId);
14810
+ newTargets = protectNewTagMutations(args.messages, args.targets, retainedThinking, args.thinkingBindingRecoveryEnabledForModel === true);
14811
+ }
14812
+ const reclaimDistance = isCacheBustingPass ? toolReclaimDistanceEntries(args.messages, args.targets) : [];
14431
14813
  const canEditWholePrefix = isCacheBustingPass && !activeThinkingPrefixHold;
14432
14814
  const previousTemporalDecisions = args.temporalCandidates && !compactionOff ? new Map([
14433
14815
  ...args.temporalObservedDecisions ?? [],
@@ -14570,7 +14952,7 @@ async function runPostTransformPhase(args) {
14570
14952
  pendingOpsDidMutate = applyPendingOperations(args.sessionId, args.db, newTargets, new Set([
14571
14953
  ...args.contextUsage.percentage >= 95 ? [] : args.protectedTagIds,
14572
14954
  ...protectedToolTags
14573
- ]), undefined, pendingOps, [], new Set, (reduction) => droppedTokenReductions.push(reduction));
14955
+ ]), undefined, pendingOps, [], new Set, (reduction) => droppedTokenReductions.push(reduction), undefined, args.onPendingDropEligibility);
14574
14956
  if (pendingOpsDidMutate) {
14575
14957
  droppedCount += pendingOps.length;
14576
14958
  for (const pendingOp of pendingOps) {
@@ -14773,6 +15155,7 @@ async function runPostTransformPhase(args) {
14773
15155
  }
14774
15156
  }
14775
15157
  const editReclaim = buildEditSupersessionReclaim({
15158
+ watermark: args.sessionMeta.toolReclaimWatermark ?? 0,
14776
15159
  db: args.db,
14777
15160
  sessionId: args.sessionId,
14778
15161
  targets: newTargets,
@@ -14811,7 +15194,7 @@ async function runPostTransformPhase(args) {
14811
15194
  const tBatchFinalize = performance.now();
14812
15195
  args.batch?.finalize();
14813
15196
  if (toolReclaimApplicationOpportunity) {
14814
- const maxTagNumber = advanceToolReclaimWatermarkToCurrentMax(args.db, args.sessionId);
15197
+ const maxTagNumber = advanceToolReclaimWatermarkByDistance(args.db, args.sessionId, reclaimDistance, args.usableWindow);
14815
15198
  args.sessionMeta.toolReclaimWatermark = Math.max(args.sessionMeta.toolReclaimWatermark ?? 0, maxTagNumber);
14816
15199
  }
14817
15200
  if (autoReclaimTargetCount > 0) {
@@ -15444,6 +15827,7 @@ async function runPostTransformPhase(args) {
15444
15827
  const hygieneCalibration = sessionDecisionCalibration(args.db, args.sessionId);
15445
15828
  const hygieneUnitsVersion = transitionSessionHygieneUnits(args.db, args.sessionId, bustedThisPass, hygieneCalibration);
15446
15829
  const baseline = refreshTailHygieneBaseline({
15830
+ opencode2: args.opencode2,
15447
15831
  messages: args.messages,
15448
15832
  tags,
15449
15833
  protectedTagNumbers: args.protectedTagNumbers,
@@ -15459,7 +15843,7 @@ async function runPostTransformPhase(args) {
15459
15843
  sessionLog(args.sessionId, formatTailHygienePrefixMismatch(baseline.lastPrefixMismatch, baseline.baselineGeneration));
15460
15844
  }
15461
15845
  const tTailState = performance.now();
15462
- const structuralSignature = tailHygieneStructuralSignature(args.messages);
15846
+ const structuralSignature = tailHygieneStructuralSignature(args.messages, args.opencode2);
15463
15847
  const effective = effectiveTailHygiene(baseline);
15464
15848
  const durableGrace = baseline.evaluable && !baseline.generationInvalidated ? captureChannel1PostReduceGraceBaseline(args.db, args.sessionId, effective.u) : getChannel1NudgeState(args.db, args.sessionId);
15465
15849
  baseline.channel1PostReduceGrace = durableGrace.postReduceGracePending || durableGrace.postReduceGraceBaselineU !== undefined ? {
@@ -15471,7 +15855,7 @@ async function runPostTransformPhase(args) {
15471
15855
  ...baseline,
15472
15856
  usableWindow: args.usableWindow,
15473
15857
  realUserTurnCount: countRealUserMessages(args.messages),
15474
- reducedSinceRefresh: baseline.baselineGeneration !== previous?.baselineGeneration ? false : previous?.reducedSinceRefresh ?? false,
15858
+ reducedSinceRefresh: baseline.baselineGeneration !== previous?.baselineGeneration || (previous?.generationInvalidated === true || previous?.evaluable === false) && baseline.evaluable && !baseline.generationInvalidated ? false : previous?.reducedSinceRefresh ?? false,
15475
15859
  agentDropsAppliedThisPass: pendingOpsDidMutate,
15476
15860
  oldestReclaimableToolTags: postprocessOldestTags(args.db, args.sessionId, args.protectedTagNumbers)
15477
15861
  });
@@ -15509,7 +15893,7 @@ async function runPostTransformPhase(args) {
15509
15893
  const tTailGuard = performance.now();
15510
15894
  if (assertedBaseline) {
15511
15895
  try {
15512
- const servedSignature = tailHygieneStructuralSignature(args.messages);
15896
+ const servedSignature = tailHygieneStructuralSignature(args.messages, args.opencode2);
15513
15897
  if (!sameTailHygieneStructuralSignature(assertedBaseline.structuralSignature, servedSignature)) {
15514
15898
  sessionLog(args.sessionId, `ERROR [tail-hygiene-last-writer-mismatch]: served messages changed after tail-hygiene baseline refresh (expected messages=${assertedBaseline.structuralSignature.messageCount}, parts=[${assertedBaseline.structuralSignature.partCounts.join(",")}], bytes=${assertedBaseline.structuralSignature.totalBytes}; actual messages=${servedSignature.messageCount}, parts=[${servedSignature.partCounts.join(",")}], bytes=${servedSignature.totalBytes})`);
15515
15899
  }
@@ -15517,6 +15901,7 @@ async function runPostTransformPhase(args) {
15517
15901
  sessionLog(args.sessionId, "ERROR [tail-hygiene-last-writer-check-failed]: structural production guard failed open:", error);
15518
15902
  }
15519
15903
  assertTailHygieneContentUnchangedIfEnabled({
15904
+ opencode2: args.opencode2,
15520
15905
  messages: args.messages,
15521
15906
  tags: assertedBaseline.tags,
15522
15907
  protectedTagNumbers: assertedBaseline.protectedTagNumbers,
@@ -15584,15 +15969,27 @@ class UnmanagedOverWindowError extends Error {
15584
15969
  }
15585
15970
 
15586
15971
  // src/hooks/magic-context/unresolved-history-boundary.ts
15972
+ function storedHistoryBoundaryPresence(db, sessionId) {
15973
+ try {
15974
+ const row = db.prepare("SELECT cached_m0_last_baseline_end_message_id AS id FROM session_meta WHERE session_id = ?").get(sessionId);
15975
+ return row?.id ? hasRawSessionMessageById2(sessionId, row.id) : undefined;
15976
+ } catch {
15977
+ return;
15978
+ }
15979
+ }
15980
+
15587
15981
  class UnresolvedHistoryBoundaryError extends Error {
15588
15982
  estimatedTokens;
15589
15983
  contextLimitTokens;
15984
+ boundaryPresent;
15590
15985
  code = "UNRESOLVED_HISTORY_BOUNDARY";
15591
15986
  recoverable = true;
15592
- constructor(estimatedTokens, contextLimitTokens) {
15593
- super(renderUserFacingFailure("history_boundary_unresolved", "plain"));
15987
+ constructor(estimatedTokens, contextLimitTokens, boundaryPresent) {
15988
+ const reason = boundaryPresent === true ? "The boundary row is present in the host store but out of order relative to the loaded history. " : boundaryPresent === false ? "The boundary row is missing from the host store. " : "";
15989
+ super(reason + renderUserFacingFailure("history_boundary_unresolved", "plain"));
15594
15990
  this.estimatedTokens = estimatedTokens;
15595
15991
  this.contextLimitTokens = contextLimitTokens;
15992
+ this.boundaryPresent = boundaryPresent;
15596
15993
  this.name = "UnresolvedHistoryBoundaryError";
15597
15994
  }
15598
15995
  }
@@ -15676,6 +16073,7 @@ function resolveTransformHostSeams(deps) {
15676
16073
  };
15677
16074
  }
15678
16075
  function createTransform(deps) {
16076
+ const pendingDropEligibility = createPendingDropEligibilityCache();
15679
16077
  registerSystemDateRefresh(deps.historyRefreshSessions);
15680
16078
  const finalWireUsage = createFinalWireUsageTracker();
15681
16079
  const host = resolveTransformHostSeams(deps);
@@ -15726,7 +16124,7 @@ function createTransform(deps) {
15726
16124
  return historianRun?.toastDurationMs === undefined ? params : { ...params, toastDurationMs: historianRun.toastDurationMs };
15727
16125
  };
15728
16126
  beginLkgPass(sessionId);
15729
- clearOpenCodePendingTransformDecision(sessionId);
16127
+ clearOpenCodePendingTransformDecision(sessionId, messages);
15730
16128
  const db = deps.db;
15731
16129
  try {
15732
16130
  const tPrepare = performance.now();
@@ -16080,9 +16478,10 @@ function createTransform(deps) {
16080
16478
  const persistedUsageFreshForBoundary = persistedUsageBeforeResets && Date.now() - persistedUsageBeforeResets.updatedAt <= 10 * 60 * 1000 && (persistedUsageBeforeResets.lastObservedModelKey === null || currentModelKeyForBoundary === undefined || piModelRefToCanonical(persistedUsageBeforeResets.lastObservedModelKey) === piModelRefToCanonical(currentModelKeyForBoundary)) && (resolvedContextLimit === undefined || persistedUsageBeforeResets.lastUsageContextLimit === 0 || persistedUsageBeforeResets.lastUsageContextLimit === resolvedContextLimit) ? persistedUsageBeforeResets.usage : null;
16081
16479
  const boundaryUsageForProtectedTail = persistedUsageFreshForBoundary ?? contextUsageEarly;
16082
16480
  const boundaryUsageSource = persistedUsageFreshForBoundary ? "persisted" : "live";
16083
- const historyPolicyIdentity = historyBudgetPolicyIdentity(deps.historyBudgetPercentage, deps.executeThresholdPercentage, currentModelKeyForBoundary, deps.executeThresholdTokens);
16084
16481
  const historyContextLimit = resolvedContextLimit ?? (currentModelKeyForBoundary && persistedUsageBeforeResets?.lastObservedModelKey && piModelRefToCanonical(currentModelKeyForBoundary) === piModelRefToCanonical(persistedUsageBeforeResets.lastObservedModelKey) && persistedUsageBeforeResets.lastUsageContextLimit > 0 ? persistedUsageBeforeResets.lastUsageContextLimit : undefined);
16085
- const historyBudgetTokens = resolveHistoryBudgetTokens(deps.historyBudgetPercentage, contextUsageEarly, deps.executeThresholdPercentage, deps.getModelKey?.(sessionId), deps.executeThresholdTokens, historyContextLimit);
16482
+ const memoryProjectDirectory = compartmentDirectory || process.cwd();
16483
+ const renderOptions = createM0RenderOptions(deps, contextUsageEarly, currentModelKeyForBoundary, historyContextLimit, deps.memoryConfig?.enabled ? resolveProjectIdentityForSession(memoryProjectDirectory, deps.allowHomeProject) : undefined);
16484
+ const { historyBudgetTokens, projectPath: projectIdentity } = renderOptions;
16086
16485
  const emergencyCeilingLimit = thresholdContextLimit ?? 0;
16087
16486
  const emergencyCeilingTokens = Number.isFinite(emergencyCeilingLimit) && emergencyCeilingLimit > 0 ? Math.floor(emergencyCeilingLimit * (effectiveExecuteThresholdPercentage / 100)) : undefined;
16088
16487
  const schedulerDecision = compactionOff ? "defer" : resolveSchedulerDecision(deps.scheduler, sessionMeta, contextUsageEarly, sessionId, deps.getModelKey?.(sessionId), resolvedContextLimit);
@@ -16215,8 +16614,6 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
16215
16614
  });
16216
16615
  }
16217
16616
  logTransformTiming(sessionId, "emergencyRecoveryBlock", tFirstPass);
16218
- const memoryProjectDirectory = compartmentDirectory || process.cwd();
16219
- const projectIdentity = deps.memoryConfig?.enabled ? resolveProjectIdentityForSession(memoryProjectDirectory, deps.allowHomeProject) : undefined;
16220
16617
  if (deps.memoryConfig?.enabled) {
16221
16618
  maybeSendProjectIdentityWarning(deps, sessionId, memoryProjectDirectory, notificationParams);
16222
16619
  }
@@ -16237,6 +16634,18 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
16237
16634
  prewarmTagOwners(db, sessionId, ownerRatio);
16238
16635
  const tagOwnersReady = isTagOwnerSummaryReady(db, sessionId, ownerRatio);
16239
16636
  let triggerBoundarySnapshot;
16637
+ const dropProjectionEmergency = contextUsageEarly.percentage >= 95;
16638
+ const dropProjectionKey = () => pendingDropEligibilityKey(db, sessionId, lkgInput, {
16639
+ currentTurnId,
16640
+ model: modelForBudget,
16641
+ protectedTokens: deps.protectedTokens,
16642
+ protectedTokenTierOverrides: deps.protectedTokenTierOverrides,
16643
+ protectedTools: deps.protectedTools,
16644
+ keepReasoningTokens: deps.keepReasoningTokens,
16645
+ usableSoft: windowGeometry?.usableSoft ?? boundaryContextLimit,
16646
+ emergency: dropProjectionEmergency,
16647
+ clearedReasoningThroughTag: sessionMeta?.clearedReasoningThroughTag
16648
+ });
16240
16649
  if (fullFeatureMode && !compactionOff && historianRunnable && tagOwnersReady && !sessionMeta.compartmentInProgress) {
16241
16650
  const tTrigger = performance.now();
16242
16651
  try {
@@ -16249,7 +16658,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
16249
16658
  force: contextUsageEarly.percentage >= forceMaterializationPercentage && (contextUsageEarly.percentage >= 95 || getEmergencyInputSample(db, sessionId) === 0),
16250
16659
  explicitFlush: deps.pendingMaterializationSessions.has(sessionId),
16251
16660
  publishedHistory: isCacheBusting
16252
- });
16661
+ }, pendingDropEligibility.read(sessionId, dropProjectionKey()));
16253
16662
  if (triggerResult.shouldFire) {
16254
16663
  sessionLog(sessionId, `compartment trigger: firing (reason=${triggerResult.reason})`);
16255
16664
  updateSessionMeta(db, sessionId, { compartmentInProgress: true });
@@ -16257,8 +16666,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
16257
16666
  triggerBoundarySnapshot = triggerResult.boundarySnapshot;
16258
16667
  }
16259
16668
  } catch (error) {
16260
- passOutcome.record("compartment-trigger-failure");
16261
- sessionLog(sessionId, "compartment trigger failed (non-fatal):", error);
16669
+ recordCompartmentTriggerError(passOutcome, sessionId, error);
16262
16670
  }
16263
16671
  logTransformTiming(sessionId, "compartmentTrigger", tTrigger);
16264
16672
  }
@@ -16498,12 +16906,8 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
16498
16906
  db,
16499
16907
  sessionId,
16500
16908
  state: sessionMeta,
16501
- projectPath: projectIdentity,
16502
16909
  projectDirectory: sessionDirectory,
16503
- memoryEnabled: deps.memoryConfig?.enabled,
16504
- memoryInjectionBudgetTokens: deps.memoryConfig?.injectionBudgetTokens,
16505
- historyBudgetTokens,
16506
- historyBudgetPolicyIdentity: historyPolicyIdentity,
16910
+ ...renderOptions,
16507
16911
  hardSignals: m0HardSignals
16508
16912
  }).value;
16509
16913
  const protectionCacheBustingPass = !compactionOff && (schedulerDecision === "execute" || isCacheBusting || contextUsage.percentage >= forceMaterializationPercentage || deps.pendingMaterializationSessions.has(sessionId) || canConsumeDeferredLate && deferredMaterializationSessions.has(sessionId) || protectionFoldWillBust);
@@ -16615,6 +17019,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
16615
17019
  keepReasoningTokens: resolveKeepReasoningTokens(deps.keepReasoningTokens, currentModelKeyForBoundary),
16616
17020
  resolvedModelID: modelForBudget?.modelID,
16617
17021
  protectedTagIds,
17022
+ onPendingDropEligibility: (eligible) => pendingDropEligibility.record(sessionId, dropProjectionKey(), eligible),
16618
17023
  protectedTagNumbers,
16619
17024
  protectedCutoff,
16620
17025
  protectedCount: protectionWindow.status.protectedCount,
@@ -16642,13 +17047,8 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
16642
17047
  passOutcome,
16643
17048
  historyRefreshSessions: deps.historyRefreshSessions,
16644
17049
  m0M1: {
16645
- projectPath: projectIdentity,
16646
17050
  projectDirectory: sessionDirectory,
16647
- memoryEnabled: deps.memoryConfig?.enabled,
16648
- memoryInjectionBudgetTokens: deps.memoryConfig?.injectionBudgetTokens,
16649
- historyBudgetTokens,
16650
- historyBudgetPolicyIdentity: historyPolicyIdentity,
16651
- temporalAwareness: deps.experimentalTemporalAwareness,
17051
+ ...renderOptions,
16652
17052
  hardSignals: m0HardSignals
16653
17053
  }
16654
17054
  });
@@ -16716,7 +17116,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
16716
17116
  });
16717
17117
  if (untrimmed.tokens > boundaryContextLimit) {
16718
17118
  sessionLog(sessionId, `history boundary unresolved: prefix trim refused and the untrimmed request estimate ${untrimmed.tokens} (trusted=${untrimmed.trusted}) exceeds the context limit ${boundaryContextLimit}; not sending it`);
16719
- throw new UnresolvedHistoryBoundaryError(untrimmed.tokens, boundaryContextLimit);
17119
+ throw new UnresolvedHistoryBoundaryError(untrimmed.tokens, boundaryContextLimit, storedHistoryBoundaryPresence(db, sessionId));
16720
17120
  }
16721
17121
  }
16722
17122
  if (unmanagedOverWindowPass) {
@@ -16893,6 +17293,24 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
16893
17293
  bustedThisPass: true,
16894
17294
  lkgFrozenReleaseReason: recoveryReleaseReason
16895
17295
  });
17296
+ } else {
17297
+ noteOpenCodeUnbustedPass(sessionId, {
17298
+ tsMs: Date.now(),
17299
+ decision: schedulerDecision,
17300
+ materialized: postTransformResult.materialized,
17301
+ materializeReason: normalizeMaterializeReason("opencode", postTransformResult.materializeReason, postTransformResult.materialized),
17302
+ systemHashPrev: postTransformResult.systemHashPrev,
17303
+ systemHashNew: postTransformResult.systemHashNew,
17304
+ m0ToolSetHashPrev: postTransformResult.m0ToolSetHashPrev,
17305
+ m0ToolSetHashNew: postTransformResult.m0ToolSetHashNew,
17306
+ m0ModelKeyPrev: postTransformResult.m0ModelKeyPrev,
17307
+ m0ModelKeyNew: postTransformResult.m0ModelKeyNew,
17308
+ emergency: postTransformResult.emergency,
17309
+ droppedTokens: postTransformResult.droppedTokens,
17310
+ droppedCount: postTransformResult.droppedCount,
17311
+ inputTokens: contextUsage.inputTokens,
17312
+ bustedThisPass: false
17313
+ });
16896
17314
  }
16897
17315
  logTransformTiming(sessionId, "postTransformPhase", tPostProcess);
16898
17316
  const msgTokens = getMessageTokensCache(sessionId);
@@ -17047,11 +17465,19 @@ function clearMaintenanceSystemSnapshot(sessionId) {
17047
17465
  function readMaintenanceSystemSnapshot(sessionId) {
17048
17466
  return maintenanceSystemSnapshotBySession.get(sessionId);
17049
17467
  }
17468
+ var maintenanceModelUnconfiguredNoted = new Set;
17469
+ function noteMaintenanceModelUnconfigured(projectPath, sessionId) {
17470
+ if (maintenanceModelUnconfiguredNoted.has(projectPath))
17471
+ return;
17472
+ maintenanceModelUnconfiguredNoted.add(projectPath);
17473
+ sessionLog(sessionId, "memory maintenance skipped: neither maintenance.model nor historian.model is configured (set either in the user-level mmc config; the session model is never inherited)");
17474
+ }
17050
17475
  function createMaintenanceTailLauncher(deps) {
17051
17476
  const buildLauncher = (projectPath) => (deps.launcherFactory ?? ((db, path) => getMaintenanceLauncher(db, path, {
17052
17477
  getConfig: () => ({
17053
17478
  maintenance: {
17054
- enabled: deps.getMaintenanceConfig?.()?.enabled ?? false
17479
+ enabled: deps.getMaintenanceConfig?.()?.enabled ?? false,
17480
+ model: deps.getMaintenanceConfig?.()?.model
17055
17481
  }
17056
17482
  }),
17057
17483
  runFactory: (signals) => {
@@ -17089,6 +17515,10 @@ function createMaintenanceTailLauncher(deps) {
17089
17515
  return;
17090
17516
  if (!deps.hiddenCompletionExecutor)
17091
17517
  return;
17518
+ if (!hasMaintenancePrimary(maintenance)) {
17519
+ noteMaintenanceModelUnconfigured(projectPath, input.sessionId);
17520
+ return;
17521
+ }
17092
17522
  const poolCount = countMaintenancePool(deps.db, projectPath);
17093
17523
  const hostSystemText = readMaintenanceSystemSnapshot(input.sessionId);
17094
17524
  buildLauncher(projectPath).maybeLaunch({
@@ -17107,25 +17537,6 @@ function createMaintenanceTailLauncher(deps) {
17107
17537
  }
17108
17538
  };
17109
17539
  }
17110
- function resolveHistoryBudgetTokens(historyBudgetPercentage, contextUsage, executeThresholdPercentage, modelKey, executeThresholdTokens, resolvedContextLimit) {
17111
- if (!historyBudgetPercentage) {
17112
- return;
17113
- }
17114
- let contextLimit = resolvedContextLimit && resolvedContextLimit > 0 ? resolvedContextLimit : 0;
17115
- if (contextLimit <= 0) {
17116
- if (contextUsage.percentage <= 0) {
17117
- return;
17118
- }
17119
- contextLimit = contextUsage.inputTokens / (contextUsage.percentage / 100);
17120
- }
17121
- if (!Number.isFinite(contextLimit) || contextLimit <= 0) {
17122
- return;
17123
- }
17124
- return Math.floor(contextLimit * (resolveExecuteThreshold(executeThresholdPercentage ?? 65, modelKey, 65, {
17125
- tokensConfig: executeThresholdTokens,
17126
- contextLimit
17127
- }) / 100) * historyBudgetPercentage);
17128
- }
17129
17540
 
17130
17541
  // src/plugin/rpc-handlers.ts
17131
17542
  import { once } from "node:events";
@@ -17376,6 +17787,7 @@ function runManualMaintenanceCommand(args) {
17376
17787
  }
17377
17788
  const explanations = {
17378
17789
  "maintenance-disabled": "memory maintenance is disabled (maintenance.enabled: false)",
17790
+ "maintenance-model-unconfigured": "memory maintenance has no primary model configured — set maintenance.model in the user-level mmc config (fallback_models alone does not enable maintenance; the chain never inherits the historian or session model)",
17379
17791
  "run-in-flight-local": "a maintenance run is already in flight; next_due_at is due now, so the launcher retries on the next pass",
17380
17792
  "empty-pool": "memory pool is empty (no active/permanent rows) — nothing to maintain"
17381
17793
  };
@@ -18261,7 +18673,7 @@ function registerRpcHandlers(rpcServer, args) {
18261
18673
  });
18262
18674
  const buildManagedCtx = async (db) => {
18263
18675
  const { deriveHistorianChunkTokens, resolveHistorianContextLimit } = await import("./derive-budgets-s1p37t2g.js");
18264
- const { resolveHistorianModel } = await import("./model-resolution-wazv26n0.js");
18676
+ const { resolveHistorianModel } = await import("./model-resolution-cvyrrcjw.js");
18265
18677
  const DEFAULT_HISTORIAN_TIMEOUT_MS = 600000;
18266
18678
  const runConfig = historianRunConfig(config, currentPluginConfigReader(directory)?.poll().effective ?? config);
18267
18679
  const historianModel = resolveHistorianModel(runConfig, "opencode");
@@ -18290,8 +18702,8 @@ function registerRpcHandlers(rpcServer, args) {
18290
18702
  const db = readDatabase();
18291
18703
  if (!db)
18292
18704
  return { ok: false, error: "db unavailable" };
18293
- const { runManagedRecomp } = await import("./recomp-orchestrator-wrjy3qym.js");
18294
- const { sendIgnoredMessage } = await import("./send-session-notification-cz6ev088.js");
18705
+ const { runManagedRecomp } = await import("./recomp-orchestrator-k760qy1n.js");
18706
+ const { sendIgnoredMessage } = await import("./send-session-notification-njjf3b4s.js");
18295
18707
  log(`[rpc] recomp requested for session ${sessionId}`);
18296
18708
  if (!args.client && !args.hiddenCompletionExecutor) {
18297
18709
  return {
@@ -18340,7 +18752,7 @@ function registerRpcHandlers(rpcServer, args) {
18340
18752
  const db = readDatabase();
18341
18753
  if (!db)
18342
18754
  return { ok: false, error: "db unavailable" };
18343
- const { runManagedWrapup } = await import("./wrapup-orchestrator-bmybc5p5.js");
18755
+ const { runManagedWrapup } = await import("./wrapup-orchestrator-b2gswatj.js");
18344
18756
  const model = liveSessionState.liveModelBySession.get(sessionId);
18345
18757
  const contextLimit = model ? resolveContextLimit(model.providerID, model.modelID, { db, sessionID: sessionId }) : 128000;
18346
18758
  const ctx = {
@@ -18372,11 +18784,14 @@ function registerRpcHandlers(rpcServer, args) {
18372
18784
  const raw = String(params.subcommand ?? "status").toLowerCase();
18373
18785
  const action = raw === "run" || raw === "resume" ? raw : "status";
18374
18786
  if (action === "run") {
18375
- const maintenance = config.maintenance;
18787
+ const maintenance = inheritMaintenanceModelConfig(config);
18376
18788
  const hostSystemText = readMaintenanceSystemSnapshot(sessionId);
18377
18789
  const launcher = getMaintenanceLauncher(db, projectPath, {
18378
18790
  getConfig: () => ({
18379
- maintenance: { enabled: maintenance?.enabled ?? false }
18791
+ maintenance: {
18792
+ enabled: maintenance?.enabled ?? false,
18793
+ model: maintenance?.model
18794
+ }
18380
18795
  }),
18381
18796
  nowMs: Date.now,
18382
18797
  runFactory: createOc2MaintenanceRunFactory({
@@ -18384,7 +18799,7 @@ function registerRpcHandlers(rpcServer, args) {
18384
18799
  projectPath,
18385
18800
  executor: args.hiddenCompletionExecutor,
18386
18801
  config: () => {
18387
- const slice = config.maintenance;
18802
+ const slice = inheritMaintenanceModelConfig(config);
18388
18803
  if (!slice) {
18389
18804
  throw new Error("memory maintenance config unavailable");
18390
18805
  }
@@ -19153,8 +19568,8 @@ var v2CompactionMarkerStrategy = {
19153
19568
  import { randomUUID as randomUUID2 } from "node:crypto";
19154
19569
 
19155
19570
  // src/v2/fold/owner.ts
19156
- import { createHash as createHash4 } from "node:crypto";
19157
- var foldDigest = (value) => createHash4("sha256").update(value).digest("hex");
19571
+ import { createHash as createHash3 } from "node:crypto";
19572
+ var foldDigest = (value) => createHash3("sha256").update(value).digest("hex");
19158
19573
 
19159
19574
  class FoldOwner {
19160
19575
  storage;
@@ -20869,6 +21284,25 @@ async function cleanupLegacyHiddenChildren(db, remove, note, options = {}) {
20869
21284
  }
20870
21285
 
20871
21286
  // src/v2/hidden-completion.ts
21287
+ var DEFAULT_DISPOSE_DRAIN_MS = 2000;
21288
+ async function settleWithin(work, ms) {
21289
+ let timer;
21290
+ try {
21291
+ await Promise.race([
21292
+ work.then(() => {
21293
+ return;
21294
+ }, () => {
21295
+ return;
21296
+ }),
21297
+ new Promise((resolve) => {
21298
+ timer = setTimeout(resolve, Math.max(0, ms));
21299
+ })
21300
+ ]);
21301
+ } finally {
21302
+ if (timer)
21303
+ clearTimeout(timer);
21304
+ }
21305
+ }
20872
21306
  var POLL_INTERVAL_MS = 200;
20873
21307
  function modelKey(model) {
20874
21308
  return `${model.providerID}/${model.modelID}`;
@@ -20968,6 +21402,9 @@ function isProviderFailure(error) {
20968
21402
  }
20969
21403
  async function awaitAssistantRow(openReader, readSessionError, localFailure, childID, afterSeq, deadline, signal) {
20970
21404
  for (;; ) {
21405
+ const refusedBefore = localFailure();
21406
+ if (refusedBefore)
21407
+ throw refusedBefore;
20971
21408
  const { assistant, idle } = withReader(openReader, (reader) => ({
20972
21409
  assistant: reader.latestAssistant(childID),
20973
21410
  idle: reader.latestIdle(childID)
@@ -21043,6 +21480,9 @@ async function createV2HiddenCompletionExecutor(host, options) {
21043
21480
  const generation = options.generation ?? "opencode2";
21044
21481
  const roleTails = new Map;
21045
21482
  const note = options.log ?? log;
21483
+ let disposed;
21484
+ const openRuns = new Set;
21485
+ const inFlight = new Set;
21046
21486
  const removeSession = host.removeSession;
21047
21487
  if (!removeSession)
21048
21488
  throw new Error("Magic Context requires OpenCode 2.0.22 session.remove");
@@ -21079,7 +21519,7 @@ async function createV2HiddenCompletionExecutor(host, options) {
21079
21519
  const entry = rows.find((row) => row && typeof row === "object" && row.providerID === model.providerID && row.id === model.modelID);
21080
21520
  if (!entry)
21081
21521
  return model;
21082
- if (entry.variants && typeof entry.variants === "object" && Object.hasOwn(entry.variants, model.variant))
21522
+ if (Array.isArray(entry.variants) ? entry.variants.some((variant) => variant?.id === model.variant) : entry.variants && typeof entry.variants === "object" && Object.hasOwn(entry.variants, model.variant))
21083
21523
  return model;
21084
21524
  const key = `${model.providerID}/${model.modelID}:${model.variant}`;
21085
21525
  if (!warnedVariants.has(key)) {
@@ -21137,10 +21577,17 @@ async function createV2HiddenCompletionExecutor(host, options) {
21137
21577
  return {
21138
21578
  capabilities: V2_HIDDEN_EXECUTOR_CAPABILITIES,
21139
21579
  async open(identity) {
21580
+ if (disposed)
21581
+ throw disposed;
21140
21582
  const role = roleFor(identity);
21141
- const releaseRole = await acquireRole(role);
21583
+ const releaseStore = options.holdStore?.() ?? (() => {});
21584
+ let releaseRole;
21142
21585
  let openedChild;
21586
+ let opened;
21143
21587
  try {
21588
+ releaseRole = await acquireRole(role);
21589
+ if (disposed)
21590
+ throw disposed;
21144
21591
  await options.ensureAgent?.();
21145
21592
  const head = await resolveHead(identity);
21146
21593
  const active = await lifecycle.open(identity, role, head);
@@ -21151,17 +21598,25 @@ async function createV2HiddenCompletionExecutor(host, options) {
21151
21598
  role,
21152
21599
  child: active,
21153
21600
  releaseRole,
21601
+ releaseStore,
21154
21602
  failed: false,
21155
21603
  unsettledFailure: false,
21156
21604
  retired: false
21157
21605
  };
21158
21606
  runs.set(handle, run);
21607
+ opened = run;
21608
+ openRuns.add(run);
21609
+ if (disposed)
21610
+ noteChildOfDisposedInstance(active.id);
21159
21611
  await switchChildModel(run, head);
21160
21612
  return handle;
21161
21613
  } catch (error) {
21162
21614
  if (openedChild)
21163
21615
  lifecycle.retire(openedChild, "hidden-run-open-failed");
21164
- releaseRole();
21616
+ if (opened)
21617
+ openRuns.delete(opened);
21618
+ releaseRole?.();
21619
+ releaseStore();
21165
21620
  throw error;
21166
21621
  }
21167
21622
  },
@@ -21169,6 +21624,15 @@ async function createV2HiddenCompletionExecutor(host, options) {
21169
21624
  const run = runs.get(handle);
21170
21625
  if (!run)
21171
21626
  throw new Error("Unknown hidden completion run");
21627
+ const refuseDisposed = () => {
21628
+ if (!disposed)
21629
+ return;
21630
+ run.failed = true;
21631
+ run.unsettledFailure = true;
21632
+ retire(run, "plugin-instance-disposed");
21633
+ throw disposed;
21634
+ };
21635
+ refuseDisposed();
21172
21636
  if (request.signal?.aborted) {
21173
21637
  await interruptAndRetire(run, "aborted-before-prompt");
21174
21638
  throw new Error("Hidden completion prompt aborted");
@@ -21183,6 +21647,7 @@ async function createV2HiddenCompletionExecutor(host, options) {
21183
21647
  handle.childSessionId = run.child.id;
21184
21648
  }
21185
21649
  await switchChildModel(run, requested);
21650
+ refuseDisposed();
21186
21651
  const baseline = withReader(options.openReader, (reader) => reader.latestSequence(run.child.id));
21187
21652
  const marker = `mc:hidden:${crypto.randomUUID()}:${crypto.randomUUID()}`;
21188
21653
  run.completion = undefined;
@@ -21208,6 +21673,28 @@ async function createV2HiddenCompletionExecutor(host, options) {
21208
21673
  interruptAndRetire(run, "prompt-timeout").finally(() => abortReject(new Error("Hidden completion prompt timed out")));
21209
21674
  }, Math.max(0, deadline - Date.now()));
21210
21675
  request.signal?.addEventListener("abort", onAbort, { once: true });
21676
+ let settle;
21677
+ const entry = {
21678
+ cancel: async (refusal) => {
21679
+ attempt.refusal ??= refusal;
21680
+ noteChildOfDisposedInstance(run.child.id);
21681
+ try {
21682
+ await interruptAndRetire(run, "plugin-instance-disposed");
21683
+ } catch (error) {
21684
+ note(`[magic-context] hidden child ${run.child.id} could not be interrupted on dispose: ${errorText(error)}`);
21685
+ } finally {
21686
+ abortReject(refusal);
21687
+ }
21688
+ },
21689
+ reject: (refusal) => {
21690
+ attempt.refusal ??= refusal;
21691
+ abortReject(refusal);
21692
+ },
21693
+ settled: new Promise((resolve) => {
21694
+ settle = resolve;
21695
+ })
21696
+ };
21697
+ inFlight.add(entry);
21211
21698
  try {
21212
21699
  await Promise.race([
21213
21700
  host.prompt({ sessionID: run.child.id, text: marker }),
@@ -21278,6 +21765,8 @@ async function createV2HiddenCompletionExecutor(host, options) {
21278
21765
  clearTimeout(deadlineTimer);
21279
21766
  request.signal?.removeEventListener("abort", onAbort);
21280
21767
  options.hook.releaseAttempt(marker);
21768
+ inFlight.delete(entry);
21769
+ settle();
21281
21770
  }
21282
21771
  },
21283
21772
  async collect(handle) {
@@ -21299,9 +21788,27 @@ async function createV2HiddenCompletionExecutor(host, options) {
21299
21788
  }
21300
21789
  } finally {
21301
21790
  runs.delete(handle);
21791
+ openRuns.delete(run);
21302
21792
  run.releaseRole();
21793
+ run.releaseStore();
21303
21794
  }
21304
21795
  await lifecycle.finish(run.child, run.retired);
21796
+ },
21797
+ async dispose() {
21798
+ disposed ??= new HiddenCompletionRefusal("hidden_instance_disposed", HIDDEN_RUN_DISPOSED_REASON, true);
21799
+ const refusal = disposed;
21800
+ for (const run of openRuns)
21801
+ noteChildOfDisposedInstance(run.child.id);
21802
+ const entries = [...inFlight];
21803
+ if (entries.length === 0)
21804
+ return;
21805
+ note(`[magic-context] plugin instance disposed with ${entries.length} hidden run(s) in flight; cancelling them`);
21806
+ const bound = options.disposeDrainMs ?? DEFAULT_DISPOSE_DRAIN_MS;
21807
+ const deadline = Date.now() + bound;
21808
+ await settleWithin(Promise.allSettled(entries.map((entry) => entry.cancel(refusal))), bound);
21809
+ for (const entry of entries)
21810
+ entry.reject(refusal);
21811
+ await settleWithin(Promise.allSettled(entries.map((entry) => entry.settled)), deadline - Date.now());
21305
21812
  }
21306
21813
  };
21307
21814
  }
@@ -21588,9 +22095,9 @@ function hiddenTerminalError(event) {
21588
22095
  }
21589
22096
 
21590
22097
  // src/v2/hooks/lkg-system.ts
21591
- import { createHash as createHash5 } from "node:crypto";
22098
+ import { createHash as createHash4 } from "node:crypto";
21592
22099
  function identity(slot) {
21593
- return createHash5("sha256").update(JSON.stringify({
22100
+ return createHash4("sha256").update(JSON.stringify({
21594
22101
  capturedAt: slot.capturedAt,
21595
22102
  captureSequence: slot.captureSequence,
21596
22103
  rowVersion: slot.rowVersion,
@@ -22553,7 +23060,7 @@ function resolveCtxExpandMode(args, domain) {
22553
23060
  }
22554
23061
 
22555
23062
  // src/tools/ctx-expand/render.ts
22556
- import { createHash as createHash6 } from "node:crypto";
23063
+ import { createHash as createHash5 } from "node:crypto";
22557
23064
  function isRecord4(value) {
22558
23065
  return value !== null && typeof value === "object" && !Array.isArray(value);
22559
23066
  }
@@ -22777,7 +23284,7 @@ function renderItemByTag(db, sessionId, number, textIndexDomain = "part") {
22777
23284
  const index = scoped ? Number(scoped[2]) : 0;
22778
23285
  const piText = message.parts.filter((part) => isRecord4(part) && part.type === "text");
22779
23286
  const part = message.parts[index];
22780
- const matching = derived ? piText.filter((part) => isRecord4(part) && typeof part.text === "string" && createHash6("sha256").update(part.text).digest("hex") === derived[3]) : [];
23287
+ const matching = derived ? piText.filter((part) => isRecord4(part) && typeof part.text === "string" && createHash5("sha256").update(part.text).digest("hex") === derived[3]) : [];
22781
23288
  const selected = derived ? matching[Number(derived[4])] : textIndexDomain === "text" ? piText[index] : part;
22782
23289
  if (!selected)
22783
23290
  return `Tag ${number}'s original text part is no longer in stored history.`;
@@ -24128,6 +24635,7 @@ function persistV2UsageReading(args) {
24128
24635
 
24129
24636
  // src/v2/hooks/context.ts
24130
24637
  var HIDDEN_SESSION_ERROR_GRACE_MS = 50;
24638
+ var STORE_POOL_DISPOSE_WAIT_MS = 1000;
24131
24639
 
24132
24640
  class V2LkgAdmissionReplay extends Error {
24133
24641
  }
@@ -24467,6 +24975,7 @@ async function registerContext(context) {
24467
24975
  ensureAgent: () => hiddenAgentsReady ??= context.agent.reload(),
24468
24976
  modelCatalog: () => Promise.resolve(context.model.list()),
24469
24977
  openReader: openStoreReader,
24978
+ holdStore: () => readerPool.hold(),
24470
24979
  resolveStepCap: resolveHiddenStepCap
24471
24980
  });
24472
24981
  };
@@ -25073,7 +25582,7 @@ async function registerContext(context) {
25073
25582
  sessionDirectoryBySession: sessionDirectories,
25074
25583
  projectPath: directory,
25075
25584
  agentBySession: agents,
25076
- maintenanceConfig: config.maintenance ? () => config.maintenance : undefined,
25585
+ maintenanceConfig: config.maintenance ? () => inheritMaintenanceModelConfig(config) : undefined,
25077
25586
  hiddenCompletionExecutor,
25078
25587
  historianRunnable: !compactionOff && hiddenCompletionExecutor !== undefined && config.historian?.disable !== true,
25079
25588
  historianModel: historianModels.primary,
@@ -25386,6 +25895,7 @@ ${baseline}
25386
25895
  }, 0);
25387
25896
  return {
25388
25897
  async dispose() {
25898
+ await hiddenCompletionExecutor?.dispose();
25389
25899
  rpcStopped = true;
25390
25900
  rpcServer.stop();
25391
25901
  tools?.dispose();
@@ -25396,7 +25906,8 @@ ${baseline}
25396
25906
  release();
25397
25907
  rawProviders.clear();
25398
25908
  restoredRows.clear();
25399
- readerPool.close();
25909
+ if (!await readerPool.closeWhenIdle(STORE_POOL_DISPOSE_WAIT_MS))
25910
+ log(`[magic-context] v2 store reader pool still held by ${readerPool.holdCount()} hidden run(s) at dispose; it closes when they finish`);
25400
25911
  clearSyntheticCandidates(context);
25401
25912
  }
25402
25913
  };