@cortexkit/opencode-magic-context 0.42.4 → 0.42.6

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 (144) hide show
  1. package/README.md +2 -2
  2. package/dist/config/index.d.ts +9 -0
  3. package/dist/config/index.d.ts.map +1 -1
  4. package/dist/config/schema/magic-context.d.ts +1 -0
  5. package/dist/config/schema/magic-context.d.ts.map +1 -1
  6. package/dist/{conflict-warning-hook-jyj54gbe.js → conflict-warning-hook-aktepwpz.js} +3 -3
  7. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  8. package/dist/features/magic-context/context-authority.d.ts +28 -1
  9. package/dist/features/magic-context/context-authority.d.ts.map +1 -1
  10. package/dist/features/magic-context/dreamer/curate-category-rotation.d.ts +26 -0
  11. package/dist/features/magic-context/dreamer/curate-category-rotation.d.ts.map +1 -0
  12. package/dist/features/magic-context/dreamer/open-opencode-db.d.ts.map +1 -1
  13. package/dist/features/magic-context/dreamer/storage-task-schedule.d.ts +7 -5
  14. package/dist/features/magic-context/dreamer/storage-task-schedule.d.ts.map +1 -1
  15. package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
  16. package/dist/features/magic-context/dreamer/task-gates.d.ts +1 -0
  17. package/dist/features/magic-context/dreamer/task-gates.d.ts.map +1 -1
  18. package/dist/features/magic-context/dreamer/task-prompts.d.ts +3 -0
  19. package/dist/features/magic-context/dreamer/task-prompts.d.ts.map +1 -1
  20. package/dist/features/magic-context/dreamer/task-registry.d.ts +6 -0
  21. package/dist/features/magic-context/dreamer/task-registry.d.ts.map +1 -1
  22. package/dist/features/magic-context/dreamer/task-scheduler.d.ts +5 -1
  23. package/dist/features/magic-context/dreamer/task-scheduler.d.ts.map +1 -1
  24. package/dist/features/magic-context/memory/embedding-local.d.ts +12 -0
  25. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  26. package/dist/features/magic-context/memory/embedding.d.ts +1 -1
  27. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  28. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  29. package/dist/features/magic-context/migrations.d.ts +12 -0
  30. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  31. package/dist/features/magic-context/project-embedding-registry.d.ts +1 -0
  32. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  33. package/dist/features/magic-context/session-project-backfill.d.ts.map +1 -1
  34. package/dist/features/magic-context/storage-db.d.ts +1 -1
  35. package/dist/features/magic-context/tool-owner-backfill.d.ts.map +1 -1
  36. package/dist/hooks/auto-update-checker/index.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/cache-busting-signals.d.ts +0 -1
  38. package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
  39. package/dist/hooks/magic-context/channel2-delivery.d.ts +8 -0
  40. package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
  41. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts +12 -0
  42. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  43. package/dist/hooks/magic-context/hook.d.ts +4 -4
  44. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/module-state-sync.d.ts +3 -0
  46. package/dist/hooks/magic-context/module-state-sync.d.ts.map +1 -1
  47. package/dist/hooks/magic-context/module-transport.d.ts +24 -2
  48. package/dist/hooks/magic-context/module-transport.d.ts.map +1 -1
  49. package/dist/hooks/magic-context/module-wire.d.ts +1 -1
  50. package/dist/hooks/magic-context/module-wire.d.ts.map +1 -1
  51. package/dist/hooks/magic-context/producer-window-guard.d.ts +31 -1
  52. package/dist/hooks/magic-context/producer-window-guard.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/read-session-chunk.d.ts +6 -0
  54. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/read-session-db.d.ts +8 -0
  56. package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/read-session-formatting.d.ts +6 -0
  58. package/dist/hooks/magic-context/read-session-formatting.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/rust-mode-transform.d.ts +30 -0
  60. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  61. package/dist/hooks/magic-context/rust-refusal-recovery.d.ts +28 -0
  62. package/dist/hooks/magic-context/rust-refusal-recovery.d.ts.map +1 -0
  63. package/dist/hooks/magic-context/tail-hygiene-walk.d.ts.map +1 -1
  64. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/transform.d.ts +7 -0
  66. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  67. package/dist/{index-bkg2fdj1.js → index-4wwr6cz0.js} +3 -0
  68. package/dist/{index-jkrqhc7y.js → index-a9ssj59b.js} +94 -15
  69. package/dist/{index-faay035c.js → index-djp6rwz6.js} +484 -57
  70. package/dist/{index-p474rw9p.js → index-ngjmj6xx.js} +1 -1
  71. package/dist/{index-d8mgqezq.js → index-s61hh7gv.js} +50 -17
  72. package/dist/{index-1ar7b3e9.js → index-znraa0kv.js} +1 -1
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +1667 -380
  75. package/dist/plugin/embedding-bootstrap.d.ts.map +1 -1
  76. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  77. package/dist/plugin/memory-id-translation.d.ts +23 -0
  78. package/dist/plugin/memory-id-translation.d.ts.map +1 -0
  79. package/dist/plugin/rust-tool-backends.d.ts +1 -1
  80. package/dist/plugin/rust-tool-backends.d.ts.map +1 -1
  81. package/dist/{recomp-orchestrator-c4sa354a.js → recomp-orchestrator-0xx1364v.js} +4 -4
  82. package/dist/{send-session-notification-xnypkxpx.js → send-session-notification-jbxknvar.js} +2 -2
  83. package/dist/shared/auto-update-provenance.d.ts +9 -0
  84. package/dist/shared/auto-update-provenance.d.ts.map +1 -0
  85. package/dist/shared/conflict-detector.d.ts +5 -1
  86. package/dist/shared/conflict-detector.d.ts.map +1 -1
  87. package/dist/shared/opencode-db-path.d.ts +31 -2
  88. package/dist/shared/opencode-db-path.d.ts.map +1 -1
  89. package/dist/shared/prompt-surface-runtime.d.ts +3 -3
  90. package/dist/shared/prompt-surface-runtime.d.ts.map +1 -1
  91. package/dist/{storage-meta-session-x3j0q9qy.js → storage-meta-session-z818pn1e.js} +2 -2
  92. package/dist/{task-config-ys0arma5.js → task-config-cv8k9y8r.js} +1 -1
  93. package/dist/tools/ctx-expand/mode.d.ts +31 -0
  94. package/dist/tools/ctx-expand/mode.d.ts.map +1 -0
  95. package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
  96. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  97. package/dist/tools/ctx-note/constants.d.ts +1 -1
  98. package/dist/tools/ctx-note/constants.d.ts.map +1 -1
  99. package/dist/tools/ctx-note/tools.d.ts.map +1 -1
  100. package/dist/tools/ctx-note/types.d.ts +1 -1
  101. package/dist/tools/ctx-note/types.d.ts.map +1 -1
  102. package/dist/tools/ctx-reduce/constants.d.ts +1 -1
  103. package/dist/tools/ctx-reduce/constants.d.ts.map +1 -1
  104. package/dist/tools/ctx-search/types.d.ts +1 -1
  105. package/dist/tools/ctx-search/types.d.ts.map +1 -1
  106. package/dist/tools/light-descriptions.d.ts +2 -2
  107. package/dist/tools/light-descriptions.d.ts.map +1 -1
  108. package/dist/v2/hidden-completion.d.ts +69 -13
  109. package/dist/v2/hidden-completion.d.ts.map +1 -1
  110. package/dist/v2/hooks/context.d.ts +13 -1
  111. package/dist/v2/hooks/context.d.ts.map +1 -1
  112. package/dist/v2/hooks/hidden-child.d.ts +31 -0
  113. package/dist/v2/hooks/hidden-child.d.ts.map +1 -0
  114. package/dist/v2/hooks/store.d.ts.map +1 -1
  115. package/dist/v2/hooks/types.d.ts +99 -19
  116. package/dist/v2/hooks/types.d.ts.map +1 -1
  117. package/dist/v2/server.d.ts +19 -0
  118. package/dist/v2/server.d.ts.map +1 -1
  119. package/dist/v2/server.js +2311 -2724
  120. package/dist/v2/store-reader.d.ts +11 -5
  121. package/dist/v2/store-reader.d.ts.map +1 -1
  122. package/dist/v2/tui/index.d.ts +10 -0
  123. package/dist/v2/tui/index.d.ts.map +1 -0
  124. package/dist/v2/tui/types.d.ts +80 -0
  125. package/dist/v2/tui/types.d.ts.map +1 -0
  126. package/package.json +5 -4
  127. package/src/agents/magic-context-prompt.ts +1 -1
  128. package/src/config/index.ts +31 -1
  129. package/src/config/protected-tokens.test.ts +40 -0
  130. package/src/config/schema/magic-context.ts +8 -3
  131. package/src/features/magic-context/dreamer/task-registry.ts +10 -0
  132. package/src/shared/auto-update-provenance.ts +42 -0
  133. package/src/shared/conflict-detector.test.ts +53 -0
  134. package/src/shared/conflict-detector.ts +88 -19
  135. package/src/shared/models-dev-cache.test.ts +25 -0
  136. package/src/shared/opencode-db-path.test.ts +96 -0
  137. package/src/shared/opencode-db-path.ts +146 -15
  138. package/src/shared/prompt-surface-a1-golden.md +12 -16
  139. package/src/shared/prompt-surface-runtime.test.ts +20 -0
  140. package/src/shared/prompt-surface-runtime.ts +7 -5
  141. package/src/tui/entry.mjs +11 -1
  142. package/src/v2/tui/host-contract.test.ts +202 -0
  143. package/src/v2/tui/index.ts +270 -0
  144. package/src/v2/tui/types.ts +67 -0
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ import {
32
32
  resolveModelConfigOrDefault,
33
33
  DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE,
34
34
  DEFAULT_HISTORIAN_TIMEOUT_MS,
35
+ PROTECTED_TOKENS_MIN,
35
36
  DEFAULT_LOCAL_EMBEDDING_MODEL,
36
37
  PER_HARNESS_MIGRATION_INVENTORY,
37
38
  PER_HARNESS_MODEL_KEYS,
@@ -72,7 +73,10 @@ import {
72
73
  prepareAuthority,
73
74
  drainAuthority,
74
75
  getMemoryMirrorStatus,
76
+ moduleMemoryIdentityForHostId,
77
+ hostMemoryIdentityForModuleId,
75
78
  applyMirroredNoteCompileFields,
79
+ applyTargetedMemoryMirrorRow,
76
80
  drainMirrorPages,
77
81
  pullMemoryMirrorOnce,
78
82
  V2_MEMORY_CATEGORIES,
@@ -207,6 +211,7 @@ import {
207
211
  primeInMemoryTailRawMessageCache,
208
212
  readRawSessionMessageOrdinalPage,
209
213
  getRawSessionStoredMessageCount,
214
+ readRawSessionMessageIdOrdinals,
210
215
  readRawSessionMessagePartsById,
211
216
  readRawSessionMessageOrdinalById,
212
217
  readRawSessionMessageById,
@@ -365,11 +370,13 @@ import {
365
370
  detectThinkingBindingMismatch,
366
371
  isFable51ThinkingBindingModel,
367
372
  detectOverflow,
373
+ parseProviderModel,
368
374
  modelBodyField,
369
375
  toModelEntry,
370
376
  getPromptFailureDetail,
371
377
  promptSyncWithValidatedOutputRetry,
372
378
  normalizeSDKResponse,
379
+ ACTIVE_TOOL_IDS,
373
380
  promptSurfaceHashMaterial,
374
381
  createPromptSurfaceRuntime,
375
382
  createPromptSurfaceGuidanceEpochCache,
@@ -422,6 +429,7 @@ import {
422
429
  embedShadowTextForProject,
423
430
  enqueueShadowEmbeddingItems,
424
431
  registerProjectInObservationMode,
432
+ unregisterProjectShadowEmbedding,
425
433
  getProjectEmbeddingSnapshot,
426
434
  embedTextForProject,
427
435
  embedBatchForProject,
@@ -547,7 +555,7 @@ import {
547
555
  setRecompTerminal2,
548
556
  runManagedRecomp2,
549
557
  runManagedUpgrade2
550
- } from "./index-faay035c.js";
558
+ } from "./index-djp6rwz6.js";
551
559
  import {
552
560
  stripJsonComments,
553
561
  isPrototypePollutionKey,
@@ -576,6 +584,7 @@ import {
576
584
  } from "./index-f9gem5cr.js";
577
585
  import {
578
586
  resolveOpenCodeDbPath,
587
+ assertOpenCodeStoreGeneration,
579
588
  openCodeDbPathExists,
580
589
  formatOpenCodeDbMissingBanner,
581
590
  formatOpenCodeDbMissingStatusLine,
@@ -592,9 +601,10 @@ import {
592
601
  observeOpenCodeTurnEvent,
593
602
  clearTrackedOpenCodeSession,
594
603
  assistantAwaitingTools,
604
+ latestPersistedMessageForRecovery,
595
605
  getMessageTimesFromOpenCodeDb,
596
606
  findLastAssistantModelFromOpenCodeDb
597
- } from "./index-jkrqhc7y.js";
607
+ } from "./index-a9ssj59b.js";
598
608
  import {
599
609
  getErrorMessage,
600
610
  describeError,
@@ -603,13 +613,13 @@ import {
603
613
  clearIgnoredMessages2,
604
614
  observeIgnoredNotificationEvent2,
605
615
  sendStatusNotification2
606
- } from "./index-p474rw9p.js";
616
+ } from "./index-ngjmj6xx.js";
607
617
  import {
608
618
  detectConflicts,
609
619
  resolveCompactionForBoot,
610
620
  sendConflictWarning2,
611
621
  cleanupConflictWarnings2
612
- } from "./index-d8mgqezq.js";
622
+ } from "./index-s61hh7gv.js";
613
623
  import {
614
624
  resolveHistorianModel2,
615
625
  resolveOpenCodeAgentOverrides2,
@@ -626,7 +636,7 @@ import {
626
636
  compareTaskOrder,
627
637
  buildDreamTaskRuntimeConfigs2,
628
638
  userMemoryCollectionEnabled2
629
- } from "./index-bkg2fdj1.js";
639
+ } from "./index-4wwr6cz0.js";
630
640
  import {
631
641
  logSlowWriteTransaction,
632
642
  getOrCreateSessionMeta2,
@@ -636,7 +646,7 @@ import {
636
646
  retryPendingSessionCleanups2,
637
647
  retryPendingRustSessionCleanupsForProject2,
638
648
  clearSession2
639
- } from "./index-1ar7b3e9.js";
649
+ } from "./index-znraa0kv.js";
640
650
  import {
641
651
  registerNotificationSink2,
642
652
  pushNotification2,
@@ -10370,6 +10380,9 @@ function redactConfigValue(value) {
10370
10380
  return typeof value;
10371
10381
  }
10372
10382
  var warnedProtectedTagsDeprecation = false;
10383
+ function formatProtectedTokensBelowMinWarning(value) {
10384
+ return `protected_tokens is a token floor (minimum ${PROTECTED_TOKENS_MIN}, default derived from the context window); ${value} looks like the old protected_tags count. Remove the key to use the default, or set a token count such as 16000.`;
10385
+ }
10373
10386
  function warnProtectedTagsDeprecationOnce() {
10374
10387
  if (!warnedProtectedTagsDeprecation) {
10375
10388
  warnedProtectedTagsDeprecation = true;
@@ -10460,6 +10473,11 @@ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
10460
10473
  delete patched[key];
10461
10474
  const defaultVal = defaults[key];
10462
10475
  const reason = customMessagesByKey.get(key);
10476
+ const invalidRawValue = rawConfig[key];
10477
+ if (key === "protected_tokens" && typeof invalidRawValue === "number" && invalidRawValue < PROTECTED_TOKENS_MIN) {
10478
+ warnings.push(formatProtectedTokensBelowMinWarning(invalidRawValue));
10479
+ continue;
10480
+ }
10463
10481
  warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultVal)}.${reason ? ` ${reason}` : ""}`);
10464
10482
  }
10465
10483
  const retryMigrated = migrateLegacyAgentEnabledInMemory(migrateDreamerV2(migrateLegacyExperimental(patched, preMigrationWarnings), preMigrationWarnings), preMigrationWarnings);
@@ -10702,6 +10720,12 @@ function openOpenCodeDb() {
10702
10720
  }
10703
10721
  try {
10704
10722
  const db = new Database(dbPath, { readonly: true });
10723
+ try {
10724
+ assertOpenCodeStoreGeneration(db, "v1", dbPath);
10725
+ } catch (error) {
10726
+ db.close();
10727
+ throw error;
10728
+ }
10705
10729
  db.exec("PRAGMA busy_timeout = 5000");
10706
10730
  clearOpenCodeDbReadFailure();
10707
10731
  return db;
@@ -10781,9 +10805,11 @@ function buildCuratePrompt(args) {
10781
10805
 
10782
10806
  **Project:** ${args.projectPath}
10783
10807
 
10808
+ This run covers the whole of the \`${args.category}\` category (the other categories run in later windows).
10809
+
10784
10810
  The memories below are assumed ACCURATE (a separate verify task keeps them true). Your job is pool QUALITY: remove duplicates, tighten wording, and consolidate redundant entries that waste the ~6000-token injection budget. Explain each action in one line first. Do NOT mint new facts (that is the historian's job).
10785
10811
 
10786
- Work ALL THREE phases below in order (A → B → C) over the whole pool. Do NOT stop after consolidating — a run that only merges and never improves or archives is incomplete.
10812
+ Work ALL THREE phases below in order (A → B → C) over this category. Do NOT stop after consolidating — a run that only merges and never improves or archives is incomplete.
10787
10813
 
10788
10814
  ### Phase A — Consolidate duplicates
10789
10815
  Group by category, then merge near-identical / superset-subset / same-fact-different-angle clusters into one canonical memory with \`ctx_memory(action="merge", ids=[...], content="...", category="...")\`. Preserve every unique detail; terse present tense; paths/keys verbatim. Every id in a merge MUST share the same category — the system rejects cross-category merges. If two similar memories sit in different categories they are NOT duplicates; do not archive either as a consolidation. One fact per memory.
@@ -11003,6 +11029,7 @@ function buildDreamTaskPrompt(task, args) {
11003
11029
  case "curate":
11004
11030
  return buildCuratePrompt({
11005
11031
  projectPath: args.projectPath,
11032
+ category: args.curate?.category ?? "PROJECT_RULES",
11006
11033
  memories: args.curate?.memories ?? []
11007
11034
  });
11008
11035
  case "maintain-docs":
@@ -11189,7 +11216,19 @@ async function runSessionProjectBackfill(db, source, options = {}) {
11189
11216
  let identityResolutionsSinceYield = 0;
11190
11217
  let afterSessionId = null;
11191
11218
  for (;; ) {
11192
- const sourcePage = await readPage(afterSessionId, SESSION_PAGE_SIZE);
11219
+ let sourcePage;
11220
+ try {
11221
+ sourcePage = await readPage(afterSessionId, SESSION_PAGE_SIZE);
11222
+ } catch (error) {
11223
+ try {
11224
+ if (!markBackfillRetryPending(db, harness, holderId, now())) {
11225
+ log("[session-projects] backfill lease changed before failure cleanup");
11226
+ }
11227
+ } catch (releaseError) {
11228
+ log(`[session-projects] failed to make backfill lease retryable after discovery failed: ${releaseError}`);
11229
+ }
11230
+ throw error;
11231
+ }
11193
11232
  if (sourcePage.length === 0)
11194
11233
  break;
11195
11234
  afterSessionId = sourcePage.at(-1)?.sessionId ?? afterSessionId;
@@ -11520,17 +11559,47 @@ async function runDeferredV22Backfill(db, options = {}) {
11520
11559
  }
11521
11560
 
11522
11561
  // src/hooks/auto-update-checker/index.ts
11523
- import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync4, renameSync as renameSync3, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
11562
+ import { existsSync as existsSync7, mkdirSync, readFileSync as readFileSync5, renameSync as renameSync3, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
11524
11563
  import { dirname as dirname4, join as join4 } from "node:path";
11525
11564
 
11565
+ // src/shared/auto-update-provenance.ts
11566
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
11567
+ var AUTO_UPDATE_CHECK_STATE_FILENAME = "last-update-check.json";
11568
+ function readAutoUpdateCheckState(path) {
11569
+ if (!existsSync5(path))
11570
+ return null;
11571
+ try {
11572
+ const value = JSON.parse(readFileSync3(path, "utf-8"));
11573
+ if (!value || typeof value !== "object" || Array.isArray(value))
11574
+ return null;
11575
+ const raw = value;
11576
+ const state = {};
11577
+ if (typeof raw.lastCheckedMs === "number" && Number.isFinite(raw.lastCheckedMs)) {
11578
+ state.lastCheckedMs = raw.lastCheckedMs;
11579
+ }
11580
+ if (typeof raw.updaterPinnedSpec === "string" && raw.updaterPinnedSpec.length > 0) {
11581
+ state.updaterPinnedSpec = raw.updaterPinnedSpec;
11582
+ }
11583
+ if (typeof raw.updaterPinnedAt === "number" && Number.isFinite(raw.updaterPinnedAt)) {
11584
+ state.updaterPinnedAt = raw.updaterPinnedAt;
11585
+ }
11586
+ return state;
11587
+ } catch {
11588
+ return null;
11589
+ }
11590
+ }
11591
+ function isUpdaterPinnedSpec(state, configuredSpec) {
11592
+ return state?.updaterPinnedSpec === configuredSpec && typeof state.updaterPinnedAt === "number" && Number.isFinite(state.updaterPinnedAt);
11593
+ }
11594
+
11526
11595
  // src/hooks/auto-update-checker/checker.ts
11527
11596
  var import_comment_json = __toESM(require_src2(), 1);
11528
11597
  import { spawn } from "node:child_process";
11529
11598
  import {
11530
11599
  accessSync,
11531
- existsSync as existsSync5,
11600
+ existsSync as existsSync6,
11532
11601
  constants as fsConstants,
11533
- readFileSync as readFileSync3,
11602
+ readFileSync as readFileSync4,
11534
11603
  renameSync as renameSync2,
11535
11604
  statSync as statSync2,
11536
11605
  writeFileSync as writeFileSync2
@@ -11640,9 +11709,9 @@ function getWinningConfig(directory, surface) {
11640
11709
  let winner = null;
11641
11710
  for (const path of getSurfaceConfigPaths(directory, surface)) {
11642
11711
  try {
11643
- if (!existsSync5(path))
11712
+ if (!existsSync6(path))
11644
11713
  continue;
11645
- const parsed = parseJsonConfig(readFileSync3(path, "utf-8"));
11714
+ const parsed = parseJsonConfig(readFileSync4(path, "utf-8"));
11646
11715
  const entries = getPluginEntries(parsed);
11647
11716
  if (entries.some((entry) => entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`))) {
11648
11717
  if (winner) {
@@ -11675,9 +11744,9 @@ function findPackageJsonUp(startPath) {
11675
11744
  let dir = stat.isDirectory() ? startPath : dirname3(startPath);
11676
11745
  for (let i = 0;i < 10; i++) {
11677
11746
  const pkgPath = join3(dir, "package.json");
11678
- if (existsSync5(pkgPath)) {
11747
+ if (existsSync6(pkgPath)) {
11679
11748
  try {
11680
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync3(pkgPath, "utf-8")));
11749
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync4(pkgPath, "utf-8")));
11681
11750
  if (pkg.success && pkg.data.name === PACKAGE_NAME)
11682
11751
  return pkgPath;
11683
11752
  } catch {}
@@ -11693,9 +11762,9 @@ function findPackageJsonUp(startPath) {
11693
11762
  function getLocalDevPath(directory) {
11694
11763
  for (const configPath of getConfigPaths(directory)) {
11695
11764
  try {
11696
- if (!existsSync5(configPath))
11765
+ if (!existsSync6(configPath))
11697
11766
  continue;
11698
- const rawConfig = parseJsonConfig(readFileSync3(configPath, "utf-8"));
11767
+ const rawConfig = parseJsonConfig(readFileSync4(configPath, "utf-8"));
11699
11768
  const plugins = getPluginEntries(rawConfig);
11700
11769
  for (const entry of plugins) {
11701
11770
  if (entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`))
@@ -11705,7 +11774,7 @@ function getLocalDevPath(directory) {
11705
11774
  const pkgPath = findPackageJsonUp(localPath);
11706
11775
  if (!pkgPath)
11707
11776
  continue;
11708
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync3(pkgPath, "utf-8")));
11777
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync4(pkgPath, "utf-8")));
11709
11778
  if (pkg.success && pkg.data.name === PACKAGE_NAME)
11710
11779
  return localPath;
11711
11780
  }
@@ -11722,7 +11791,7 @@ function getLocalDevVersion(directory) {
11722
11791
  const pkgPath = findPackageJsonUp(localPath);
11723
11792
  if (!pkgPath)
11724
11793
  return null;
11725
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync3(pkgPath, "utf-8")));
11794
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync4(pkgPath, "utf-8")));
11726
11795
  return pkg.success ? pkg.data.version ?? null : null;
11727
11796
  } catch {
11728
11797
  return null;
@@ -11779,7 +11848,7 @@ function preflightConfigUpdates(paths, version) {
11779
11848
  for (const path of paths) {
11780
11849
  try {
11781
11850
  accessSync(path, fsConstants.W_OK);
11782
- const original = readFileSync3(path, "utf-8");
11851
+ const original = readFileSync4(path, "utf-8");
11783
11852
  const updated = configUpdateContent(original, version);
11784
11853
  if (!updated)
11785
11854
  return null;
@@ -11808,7 +11877,7 @@ function restoreConfigs(snapshots) {
11808
11877
  function configsHaveSpec(paths, spec) {
11809
11878
  return paths.every((path) => {
11810
11879
  try {
11811
- const parsed = parseJsonConfig(readFileSync3(path, "utf-8"));
11880
+ const parsed = parseJsonConfig(readFileSync4(path, "utf-8"));
11812
11881
  return getPluginEntries(parsed).includes(spec);
11813
11882
  } catch {
11814
11883
  return false;
@@ -11898,9 +11967,9 @@ function getCachedVersion(_spec) {
11898
11967
  const candidates = [getCurrentRuntimePackageJsonPath()].filter(isString);
11899
11968
  for (const packageJsonPath of candidates) {
11900
11969
  try {
11901
- if (!existsSync5(packageJsonPath))
11970
+ if (!existsSync6(packageJsonPath))
11902
11971
  continue;
11903
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync3(packageJsonPath, "utf-8")));
11972
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync4(packageJsonPath, "utf-8")));
11904
11973
  if (pkg.success && pkg.data.version) {
11905
11974
  if (!_spec)
11906
11975
  cachedPackageVersion = pkg.data.version;
@@ -11942,7 +12011,6 @@ async function getLatestVersion(channel = "latest", options = {}) {
11942
12011
  // src/hooks/auto-update-checker/index.ts
11943
12012
  var DEFAULT_CHECK_INTERVAL_MS = 60 * 60 * 1000;
11944
12013
  var DEFAULT_INIT_DELAY_MS = 5000;
11945
- var TIMESTAMP_FILENAME = "last-update-check.json";
11946
12014
  var PENDING_UPDATE_FILENAME = "pending-plugin-update.json";
11947
12015
  var PENDING_UPDATE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
11948
12016
  function warn2(message) {
@@ -11994,34 +12062,50 @@ async function maybeRunCheck(ctx, options) {
11994
12062
  }
11995
12063
  await runStartupCheck(ctx, options);
11996
12064
  }
12065
+ function checkStatePath(storageDir) {
12066
+ return join4(storageDir ?? getOpenCodeStorageDir(), AUTO_UPDATE_CHECK_STATE_FILENAME);
12067
+ }
12068
+ function writeCheckState(path, state) {
12069
+ mkdirSync(dirname4(path), { recursive: true });
12070
+ const tmp = `${path}.tmp.${process.pid}`;
12071
+ writeFileSync3(tmp, `${JSON.stringify(state, null, 2)}
12072
+ `, "utf-8");
12073
+ renameSync3(tmp, path);
12074
+ }
11997
12075
  function claimCheckSlot(storageDir, intervalMs) {
11998
12076
  if (!storageDir)
11999
12077
  return true;
12000
12078
  try {
12001
- const file = join4(storageDir, TIMESTAMP_FILENAME);
12002
- if (existsSync6(file)) {
12003
- try {
12004
- const raw = JSON.parse(readFileSync4(file, "utf-8"));
12005
- const last = typeof raw.lastCheckedMs === "number" ? raw.lastCheckedMs : 0;
12006
- if (Number.isFinite(last) && Date.now() - last < intervalMs) {
12007
- return false;
12008
- }
12009
- } catch {}
12010
- }
12011
- mkdirSync(dirname4(file), { recursive: true });
12012
- const tmp = `${file}.tmp.${process.pid}`;
12013
- writeFileSync3(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
12014
- renameSync3(tmp, file);
12079
+ const file = checkStatePath(storageDir);
12080
+ const previous = readAutoUpdateCheckState(file) ?? {};
12081
+ const last = previous.lastCheckedMs ?? 0;
12082
+ if (Date.now() - last < intervalMs)
12083
+ return false;
12084
+ writeCheckState(file, { ...previous, lastCheckedMs: Date.now() });
12015
12085
  return true;
12016
12086
  } catch (err) {
12017
12087
  warn2(`[auto-update-checker] Could not coordinate via timestamp file: ${String(err)}`);
12018
12088
  return true;
12019
12089
  }
12020
12090
  }
12091
+ function recordUpdaterPinnedSpec(storageDir, spec) {
12092
+ try {
12093
+ const path = checkStatePath(storageDir);
12094
+ const previous = readAutoUpdateCheckState(path) ?? {};
12095
+ writeCheckState(path, {
12096
+ ...previous,
12097
+ updaterPinnedSpec: spec,
12098
+ updaterPinnedAt: Date.now()
12099
+ });
12100
+ } catch (err) {
12101
+ warn2(`[auto-update-checker] Could not record updater pin provenance: ${String(err)}`);
12102
+ }
12103
+ }
12021
12104
  function pendingMarkerPath(storageDir) {
12022
12105
  return join4(storageDir ?? getOpenCodeStorageDir(), PENDING_UPDATE_FILENAME);
12023
12106
  }
12024
12107
  function writePendingUpdateMarker(storageDir, update, version) {
12108
+ recordUpdaterPinnedSpec(storageDir, update.spec);
12025
12109
  try {
12026
12110
  const dir = storageDir ?? dirname4(pendingMarkerPath(storageDir));
12027
12111
  mkdirSync(dir, { recursive: true });
@@ -12036,10 +12120,10 @@ function writePendingUpdateMarker(storageDir, update, version) {
12036
12120
  }
12037
12121
  function consumePendingUpdateMarker(storageDir, loadedVersion) {
12038
12122
  const path = pendingMarkerPath(storageDir);
12039
- if (!existsSync6(path))
12123
+ if (!existsSync7(path))
12040
12124
  return;
12041
12125
  try {
12042
- const marker = JSON.parse(readFileSync4(path, "utf-8"));
12126
+ const marker = JSON.parse(readFileSync5(path, "utf-8"));
12043
12127
  const age = Date.now() - (marker.writtenAt ?? 0);
12044
12128
  const olderThanLoaded = typeof marker.version === "string" && typeof loadedVersion === "string" && (compareSemverCore(marker.version, loadedVersion) ?? 0) < 0;
12045
12129
  if (marker.version === loadedVersion && marker.spec === `${PACKAGE_NAME}@${loadedVersion}` && Array.isArray(marker.configPaths) && marker.configPaths.length === 2) {
@@ -12111,11 +12195,15 @@ async function runBackgroundUpdateCheck(ctx, options) {
12111
12195
  return;
12112
12196
  }
12113
12197
  log(`[auto-update-checker] Update available (${channel}): ${currentVersion} → ${latestVersion}`);
12114
- if (pluginInfo.isPinned) {
12115
- showToast(ctx, `Magic Context ${latestVersion}`, `v${latestVersion} available. Version is pinned; update your OpenCode plugin config to upgrade.`, "info", 8000);
12116
- log("[auto-update-checker] Version is pinned; skipping auto-update");
12198
+ const updaterOwnedPin = pluginInfo.isPinned && isUpdaterPinnedSpec(readAutoUpdateCheckState(checkStatePath(options.storageDir)), pluginInfo.entry);
12199
+ if (pluginInfo.isPinned && !updaterOwnedPin) {
12200
+ showToast(ctx, `Magic Context ${latestVersion}`, `v${latestVersion} available. This version was pinned by you; update your OpenCode plugin config to upgrade.`, "info", 8000);
12201
+ log("[auto-update-checker] Version was pinned by you; skipping auto-update");
12117
12202
  return;
12118
12203
  }
12204
+ if (updaterOwnedPin) {
12205
+ log("[auto-update-checker] Version was pinned by the updater; continuing auto-update");
12206
+ }
12119
12207
  if (!options.autoUpdate) {
12120
12208
  showToast(ctx, `Magic Context ${latestVersion}`, `v${latestVersion} available. Auto-update is disabled.`, "info", 8000);
12121
12209
  log("[auto-update-checker] Auto-update disabled, notification only");
@@ -12449,9 +12537,11 @@ class SubcModuleTransport {
12449
12537
  this.wrapupSessions.delete(args.sessionId);
12450
12538
  };
12451
12539
  const laneDeadlineMs = Date.now() + attemptTimeoutMs;
12452
- let releaseLane;
12540
+ let releaseLane = () => {};
12453
12541
  try {
12454
- releaseLane = await this.acquireCorrectnessLane(args.sessionId, args.signal, laneDeadlineMs);
12542
+ if (!args.bypassSessionLane) {
12543
+ releaseLane = await this.acquireCorrectnessLane(args.sessionId, args.signal, laneDeadlineMs);
12544
+ }
12455
12545
  } catch (error) {
12456
12546
  finishWrapupTracking();
12457
12547
  if (args.method === "state_sync" && isDeadlineFailure(error)) {
@@ -12553,12 +12643,13 @@ class SubcModuleTransport {
12553
12643
  args.onTimings?.(timings);
12554
12644
  }
12555
12645
  }
12556
- async authorityRequest(sessionId, projectRoot, method, body) {
12646
+ async authorityRequest(sessionId, projectRoot, method, body, timeoutMs) {
12557
12647
  const response = await this.call({
12558
12648
  sessionId,
12559
12649
  projectRoot,
12560
12650
  method,
12561
- body: { ...body, method, v: 1 }
12651
+ body: { ...body, method, v: 1 },
12652
+ timeoutMs
12562
12653
  });
12563
12654
  if (isRecord(response) && isRecord(response.result))
12564
12655
  return response.result;
@@ -12618,6 +12709,20 @@ class SubcModuleTransport {
12618
12709
  throw new Error("mirror.pull omitted page");
12619
12710
  return { page: response.page };
12620
12711
  }
12712
+ async mirrorMemory(args) {
12713
+ const { projectRoot, ...body } = args;
12714
+ const response = await this.authorityRequest(`mirror-memory:${args.module_row_id}`, projectRoot ?? this.bindRootForAuthority(), "mirror.memory", body, 1900);
12715
+ return {
12716
+ row: isRecord(response.row) ? response.row : null
12717
+ };
12718
+ }
12719
+ async memoryIdentityAck(args) {
12720
+ const { projectRoot, ...body } = args;
12721
+ const response = await this.authorityRequest(`memory-identity:${args.project}`, projectRoot ?? this.bindRootForAuthority(), "memory.identity.ack", body);
12722
+ return {
12723
+ acknowledged: typeof response.acknowledged === "number" ? response.acknowledged : 0
12724
+ };
12725
+ }
12621
12726
  async deleteSession(sessionId, projectRoot) {
12622
12727
  await this.call({
12623
12728
  sessionId,
@@ -13567,6 +13672,7 @@ function toRow(r) {
13567
13672
  lastStatus: r.last_status ?? null,
13568
13673
  lastError: r.last_error,
13569
13674
  retryCount: r.retry_count ?? 0,
13675
+ taskStateJson: r.last_checked_commit ?? null,
13570
13676
  lastCheckedCommit: r.last_checked_commit ?? null,
13571
13677
  lastBroadRunAt: r.last_broad_run_at ?? null,
13572
13678
  retrospectiveWatermarkMs: r.retrospective_watermark_ms ?? null
@@ -13610,7 +13716,12 @@ function writeTaskScheduleState(db, row) {
13610
13716
  retry_count = excluded.retry_count,
13611
13717
  last_checked_commit = COALESCE(excluded.last_checked_commit, task_schedule_state.last_checked_commit),
13612
13718
  ${broadCycleUpdate},
13613
- retrospective_watermark_ms = COALESCE(excluded.retrospective_watermark_ms, task_schedule_state.retrospective_watermark_ms)`).run(row.projectPath, row.task, row.lastRunAt, row.nextDueAt, row.schedule, row.lastStatus, row.lastError, row.retryCount, row.lastCheckedCommit ?? null, row.lastBroadRunAt ?? null, row.retrospectiveWatermarkMs ?? null);
13719
+ retrospective_watermark_ms = COALESCE(excluded.retrospective_watermark_ms, task_schedule_state.retrospective_watermark_ms)`).run(row.projectPath, row.task, row.lastRunAt, row.nextDueAt, row.schedule, row.lastStatus, row.lastError, row.retryCount, row.taskStateJson ?? row.lastCheckedCommit ?? null, row.lastBroadRunAt ?? null, row.retrospectiveWatermarkMs ?? null);
13720
+ }
13721
+ function writeTaskStateJson(db, projectPath, task, taskStateJson) {
13722
+ db.prepare(`INSERT INTO task_schedule_state (project_path, task, last_checked_commit)
13723
+ VALUES (?, ?, ?)
13724
+ ON CONFLICT(project_path, task) DO UPDATE SET last_checked_commit = excluded.last_checked_commit`).run(projectPath, task, taskStateJson);
13614
13725
  }
13615
13726
  function isRetrospectiveWindowProcessed(db, projectPath, windowKey) {
13616
13727
  const row = db.prepare("SELECT 1 AS one FROM retrospective_processed_windows WHERE project_path = ? AND window_key = ?").get(projectPath, windowKey);
@@ -13622,7 +13733,7 @@ function recordRetrospectiveWindowProcessed(db, projectPath, windowKey) {
13622
13733
 
13623
13734
  // src/features/magic-context/dreamer/task-executor.ts
13624
13735
  import { createHash as createHash8 } from "node:crypto";
13625
- import { existsSync as existsSync9 } from "node:fs";
13736
+ import { existsSync as existsSync10 } from "node:fs";
13626
13737
 
13627
13738
  // src/agents/dreamer.ts
13628
13739
  var DREAMER_AGENT = "dreamer";
@@ -15046,6 +15157,104 @@ function recordInvocation(args, startedAt, params) {
15046
15157
  });
15047
15158
  }
15048
15159
 
15160
+ // src/features/magic-context/dreamer/curate-category-rotation.ts
15161
+ var CURATE_MEMORY_CATEGORIES = [
15162
+ "PROJECT_RULES",
15163
+ "ARCHITECTURE",
15164
+ "CONSTRAINTS",
15165
+ "CONFIG_VALUES",
15166
+ "NAMING"
15167
+ ];
15168
+ var LEGACY_CURATE_CATEGORY_BUCKETS = {
15169
+ ARCHITECTURE_DECISIONS: "ARCHITECTURE",
15170
+ CONFIG_DEFAULTS: "CONFIG_VALUES",
15171
+ ENVIRONMENT: "CONFIG_VALUES",
15172
+ KNOWN_ISSUES: "CONSTRAINTS",
15173
+ USER_DIRECTIVES: "PROJECT_RULES",
15174
+ USER_PREFERENCES: "PROJECT_RULES",
15175
+ WORKFLOW_RULES: "PROJECT_RULES"
15176
+ };
15177
+ function curateCategoryForMemoryCategory(category) {
15178
+ return isCurateMemoryCategory(category) ? category : LEGACY_CURATE_CATEGORY_BUCKETS[category] ?? null;
15179
+ }
15180
+ function isCurateMemoryCategory(value) {
15181
+ return CURATE_MEMORY_CATEGORIES.includes(value);
15182
+ }
15183
+ function parseTaskState(raw) {
15184
+ if (!raw)
15185
+ return {};
15186
+ try {
15187
+ const parsed = JSON.parse(raw);
15188
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
15189
+ } catch {
15190
+ return {};
15191
+ }
15192
+ }
15193
+ function normalizedCursor(value) {
15194
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value % CURATE_MEMORY_CATEGORIES.length : 0;
15195
+ }
15196
+ function readState(db, projectIdentity) {
15197
+ return parseTaskState(getTaskScheduleState(db, projectIdentity, "curate")?.taskStateJson);
15198
+ }
15199
+ function categoryStartIndex(state, populated) {
15200
+ const active = state.curate?.activeCategory;
15201
+ if (isCurateMemoryCategory(active)) {
15202
+ const activeIndex = CURATE_MEMORY_CATEGORIES.indexOf(active);
15203
+ return populated.has(active) ? activeIndex : (activeIndex + 1) % CURATE_MEMORY_CATEGORIES.length;
15204
+ }
15205
+ return normalizedCursor(state.curate?.cursor);
15206
+ }
15207
+ function peekCurateCategoryScope(db, projectIdentity, memories) {
15208
+ const populated = new Set(memories.map((memory) => curateCategoryForMemoryCategory(memory.category)).filter((category) => category !== null));
15209
+ const start = categoryStartIndex(readState(db, projectIdentity), populated);
15210
+ for (let offset = 0;offset < CURATE_MEMORY_CATEGORIES.length; offset += 1) {
15211
+ const category = CURATE_MEMORY_CATEGORIES[(start + offset) % CURATE_MEMORY_CATEGORIES.length];
15212
+ const scoped = memories.filter((memory) => curateCategoryForMemoryCategory(memory.category) === category);
15213
+ if (scoped.length > 0)
15214
+ return { category, memories: scoped };
15215
+ }
15216
+ return null;
15217
+ }
15218
+ function beginCurateCategoryRun(db, projectIdentity, memories) {
15219
+ const scope = peekCurateCategoryScope(db, projectIdentity, memories);
15220
+ if (!scope)
15221
+ return null;
15222
+ const state = readState(db, projectIdentity);
15223
+ writeTaskStateJson(db, projectIdentity, "curate", JSON.stringify({
15224
+ ...state,
15225
+ curate: {
15226
+ cursor: normalizedCursor(state.curate?.cursor),
15227
+ activeCategory: scope.category
15228
+ }
15229
+ }));
15230
+ return scope;
15231
+ }
15232
+ function curateTaskStateAfterSuccess(db, projectIdentity, category) {
15233
+ const state = readState(db, projectIdentity);
15234
+ return JSON.stringify({
15235
+ ...state,
15236
+ curate: {
15237
+ cursor: (CURATE_MEMORY_CATEGORIES.indexOf(category) + 1) % CURATE_MEMORY_CATEGORIES.length
15238
+ }
15239
+ });
15240
+ }
15241
+ function getActiveCurateCategory(db, projectIdentity) {
15242
+ const active = readState(db, projectIdentity).curate?.activeCategory;
15243
+ return isCurateMemoryCategory(active) ? active : null;
15244
+ }
15245
+ function getCurateCategoryScopeRefusal(args) {
15246
+ if (args.requestedCategory && args.requestedCategory !== args.scope) {
15247
+ return `Error: Curate scope is ${args.scope}; ${args.action} cannot target category ${args.requestedCategory}.`;
15248
+ }
15249
+ for (const id of args.ids ?? []) {
15250
+ const category = args.categoryForId(id);
15251
+ if (category && category !== args.scope) {
15252
+ return `Error: Curate scope is ${args.scope}; memory ID ${id} is outside the scoped category.`;
15253
+ }
15254
+ }
15255
+ return null;
15256
+ }
15257
+
15049
15258
  // src/features/magic-context/dreamer/memory-claim-safety.ts
15050
15259
  var POLICY_SENTENCE_START = /(?:^|[.!?]\s+|\n\s*)(?:[-*]\s+|\d+[.)]\s+)?(?:you\s+|we\s+)?(?:must(?:\s+not)?|never|always|do\s+not|don't|shall\s+not)\b/i;
15051
15260
  var ACTOR_POLICY = /\b(?:you|we|agents?|masons?|workers?|operators?|users?|maintainers?)\s+(?:must(?:\s+not)?|should(?:\s+not)?|need\s+to|shall(?:\s+not)?|cannot|can't|may\s+not)\b/i;
@@ -17028,7 +17237,7 @@ async function archiveExpiredMemories(args) {
17028
17237
  }
17029
17238
 
17030
17239
  // src/features/magic-context/dreamer/maintain-docs-protected-enforcement.ts
17031
- import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
17240
+ import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
17032
17241
  import { join as join7 } from "node:path";
17033
17242
 
17034
17243
  // src/features/magic-context/dreamer/protected-regions.ts
@@ -17132,8 +17341,8 @@ function snapshotMaintainDocsFiles(docsDir) {
17132
17341
  for (const name of MAINTAIN_DOCS_SNAPSHOT_FILES) {
17133
17342
  const path = join7(docsDir, name);
17134
17343
  try {
17135
- if (existsSync7(path)) {
17136
- snapshot.set(name, readFileSync5(path, "utf8"));
17344
+ if (existsSync8(path)) {
17345
+ snapshot.set(name, readFileSync6(path, "utf8"));
17137
17346
  }
17138
17347
  } catch {}
17139
17348
  }
@@ -17143,7 +17352,7 @@ function enforceMaintainDocsProtectedRegions(args) {
17143
17352
  for (const [fileName, original] of args.snapshot) {
17144
17353
  const path = join7(args.docsDir, fileName);
17145
17354
  try {
17146
- const current = readFileSync5(path, "utf8");
17355
+ const current = readFileSync6(path, "utf8");
17147
17356
  const { text, violated } = enforceProtectedRegions(original, current);
17148
17357
  if (!violated) {
17149
17358
  continue;
@@ -17160,7 +17369,7 @@ function enforceMaintainDocsProtectedRegions(args) {
17160
17369
  import { createHash as createHash6 } from "node:crypto";
17161
17370
 
17162
17371
  // src/features/magic-context/dreamer/map-memories-prompt.ts
17163
- import { existsSync as existsSync8, statSync as statSync3 } from "node:fs";
17372
+ import { existsSync as existsSync9, statSync as statSync3 } from "node:fs";
17164
17373
  import path3 from "node:path";
17165
17374
  var MAP_MEMORIES_SYSTEM_PROMPT = `You are a memory mapper for the magic-context system. You map project memories to the repository files that back them.
17166
17375
 
@@ -17198,7 +17407,7 @@ function extractMemoryCandidatePaths(content, repoDir) {
17198
17407
  if (!abs.startsWith(`${root}/`))
17199
17408
  continue;
17200
17409
  try {
17201
- if (existsSync8(abs) && statSync3(abs).isFile())
17410
+ if (existsSync9(abs) && statSync3(abs).isFile())
17202
17411
  found.add(rel);
17203
17412
  } catch {}
17204
17413
  if (found.size >= MAX_SEED_PATHS_PER_MEMORY)
@@ -18338,12 +18547,13 @@ function countActiveMemories(db, projectPath) {
18338
18547
  }
18339
18548
  function countLiveMemories(db, projectPath, options = {}) {
18340
18549
  const unclassified = options.unclassifiedOnly && hasMemoryClassifiedAtColumn(db);
18341
- const row = db.prepare(`SELECT COUNT(*) AS cnt
18342
- FROM memories
18343
- WHERE project_path = ?
18344
- AND status IN ('active','permanent')
18345
- AND (expires_at IS NULL OR expires_at > ?)
18346
- ${unclassified ? "AND classified_at IS NULL" : ""}`).get(projectPath, Date.now());
18550
+ const sql = `SELECT COUNT(*) AS cnt
18551
+ FROM memories
18552
+ WHERE project_path = ?
18553
+ AND status IN ('active','permanent')
18554
+ AND (expires_at IS NULL OR expires_at > ?)
18555
+ ${unclassified ? "AND classified_at IS NULL" : ""}`;
18556
+ const row = options.category ? db.prepare(`${sql} AND category = ?`).get(projectPath, Date.now(), options.category) : db.prepare(sql).get(projectPath, Date.now());
18347
18557
  return row?.cnt ?? 0;
18348
18558
  }
18349
18559
  function countUnmappedActiveMemories(db, projectPath) {
@@ -18454,8 +18664,13 @@ function getDreamTaskBacklog(db, projectPath, task, options = {}) {
18454
18664
  return { pending, total };
18455
18665
  }
18456
18666
  case "curate": {
18457
- const total = countLiveMemories(db, projectPath);
18458
- return { pending: total, total };
18667
+ const categories = db.prepare(`SELECT category FROM memories
18668
+ WHERE project_path = ?
18669
+ AND status IN ('active','permanent')
18670
+ AND (expires_at IS NULL OR expires_at > ?)`).all(projectPath, Date.now());
18671
+ const scope = peekCurateCategoryScope(db, projectPath, categories);
18672
+ const total = scope?.memories.length ?? 0;
18673
+ return scope ? { pending: total, total, category: scope.category } : { pending: 0, total: 0 };
18459
18674
  }
18460
18675
  case "compress-cues": {
18461
18676
  const total = countLiveMemories(db, projectPath);
@@ -19307,6 +19522,7 @@ function createDreamTaskExecutor(deps) {
19307
19522
  processed: Math.max(0, processed),
19308
19523
  total: backlogAtStart.pending,
19309
19524
  startedAt,
19525
+ ...backlogAtStart.category ? { category: backlogAtStart.category } : {},
19310
19526
  ...refused === undefined ? {} : { refused: Math.max(0, refused) }
19311
19527
  });
19312
19528
  };
@@ -19359,7 +19575,8 @@ function createDreamTaskExecutor(deps) {
19359
19575
  totalAtStart: backlogAtStart.total,
19360
19576
  pendingAtEnd: end.pending,
19361
19577
  totalAtEnd: end.total,
19362
- processed
19578
+ processed,
19579
+ ...backlogAtStart.category ? { category: backlogAtStart.category } : {}
19363
19580
  };
19364
19581
  return value;
19365
19582
  })()
@@ -19951,8 +20168,8 @@ async function runAgenticTask(config, ctx, helpers) {
19951
20168
  const lastRunAt = getTaskScheduleState(db, projectIdentity, config.task)?.lastRunAt ?? null;
19952
20169
  const maintainDocsSnapshot = task === "maintain-docs" ? snapshotMaintainDocsFiles(docsDir) : undefined;
19953
20170
  const existingDocs = task === "maintain-docs" ? {
19954
- architecture: existsSync9(`${docsDir}/ARCHITECTURE.md`),
19955
- structure: existsSync9(`${docsDir}/STRUCTURE.md`)
20171
+ architecture: existsSync10(`${docsDir}/ARCHITECTURE.md`),
20172
+ structure: existsSync10(`${docsDir}/STRUCTURE.md`)
19956
20173
  } : undefined;
19957
20174
  const abortController = new AbortController;
19958
20175
  let leaseLost = false;
@@ -19965,6 +20182,7 @@ async function runAgenticTask(config, ctx, helpers) {
19965
20182
  let expiredArchived = 0;
19966
20183
  try {
19967
20184
  let curateMemories;
20185
+ let curateCategory;
19968
20186
  if (task === "curate") {
19969
20187
  expiredArchived = await archiveExpiredMemories({
19970
20188
  db,
@@ -19976,10 +20194,12 @@ async function runAgenticTask(config, ctx, helpers) {
19976
20194
  });
19977
20195
  if (leaseLost)
19978
20196
  throw new Error("Dream lease lost during expired-memory archive");
19979
- curateMemories = loadActiveMemoryPromptMemories(db, projectIdentity);
19980
- log(`[dreamer] curate pool: in_scope=${curateMemories.length} expired_archived=${expiredArchived}`);
19981
- if (curateMemories.length === 0 && expiredArchived > 0) {
19982
- const progress = formatExpiredArchiveProgress(expiredArchived);
20197
+ const scope = beginCurateCategoryRun(db, projectIdentity, loadActiveMemoryPromptMemories(db, projectIdentity));
20198
+ curateMemories = scope?.memories ?? [];
20199
+ curateCategory = scope?.category;
20200
+ log(`[dreamer] curate pool: category=${curateCategory ?? "none"} in_scope=${curateMemories.length} expired_archived=${expiredArchived}`);
20201
+ if (curateMemories.length === 0) {
20202
+ const progress = expiredArchived > 0 ? formatExpiredArchiveProgress(expiredArchived) : "curate: no populated project-memory category";
19983
20203
  helpers.recordRun("completed", null, {
19984
20204
  memoryChanges: helpers.computeMemoryDelta(memoryBefore),
19985
20205
  progress
@@ -19991,7 +20211,7 @@ async function runAgenticTask(config, ctx, helpers) {
19991
20211
  projectPath: projectIdentity,
19992
20212
  lastDreamAt: lastRunAt ? String(lastRunAt) : null,
19993
20213
  existingDocs,
19994
- curate: curateMemories ? { memories: curateMemories } : undefined
20214
+ curate: curateMemories && curateCategory ? { category: curateCategory, memories: curateMemories } : undefined
19995
20215
  });
19996
20216
  const createResponse = await createChildSessionWithFence({
19997
20217
  client: requireDreamClient(deps.client),
@@ -20083,16 +20303,34 @@ async function runAgenticTask(config, ctx, helpers) {
20083
20303
  }
20084
20304
  }
20085
20305
  const curateOutput = task === "curate" ? run.validated : undefined;
20306
+ const curateScopeProgress = task === "curate" && curateCategory && curateMemories ? `curate: ${curateCategory} (${curateMemories.length})` : null;
20086
20307
  const progress = [
20308
+ curateScopeProgress,
20087
20309
  expiredArchived > 0 ? formatExpiredArchiveProgress(expiredArchived) : null,
20088
20310
  curateOutput && curateOutput.memoryOperations.completedActions.length > 0 ? formatCurateMemoryOperations(curateOutput.memoryOperations.completedActions) : null,
20089
20311
  curateRefused > 0 ? `curate: refused ${curateRefused} unsafe mutation(s)` : null
20090
20312
  ].filter((value) => Boolean(value)).join("; ");
20313
+ const curateCount = task === "curate" && curateCategory ? loadActiveMemoryPromptMemories(db, projectIdentity).filter((memory) => curateCategoryForMemoryCategory(memory.category) === curateCategory).length : undefined;
20314
+ const curateBacklog = curateCount !== undefined && curateCategory ? {
20315
+ pending: curateCount,
20316
+ total: curateCount,
20317
+ category: curateCategory
20318
+ } : undefined;
20091
20319
  helpers.recordRun("completed", null, {
20092
20320
  memoryChanges: helpers.computeMemoryDelta(memoryBefore),
20093
- progress: progress || null
20321
+ progress: progress || null,
20322
+ backlogAfter: curateBacklog
20094
20323
  });
20095
- return { status: "completed", ...progress ? { detail: progress } : {} };
20324
+ return {
20325
+ status: "completed",
20326
+ ...progress ? { detail: progress } : {},
20327
+ ...curateBacklog ? { backlog: curateBacklog } : {},
20328
+ ...curateCategory ? {
20329
+ schedulePatch: {
20330
+ taskStateJson: curateTaskStateAfterSuccess(db, projectIdentity, curateCategory)
20331
+ }
20332
+ } : {}
20333
+ };
20096
20334
  } finally {
20097
20335
  heartbeat.stop();
20098
20336
  if (childSessionId)
@@ -20157,16 +20395,17 @@ function planDueTasks(db, projectIdentity, tasks, now) {
20157
20395
  }
20158
20396
  return due;
20159
20397
  }
20160
- function advanceAfterRun(db, projectIdentity, due, finishedAt, status, error, schedulePatch) {
20398
+ function advanceAfterRun(db, projectIdentity, due, finishedAt, status, error, schedulePatch, startedAt) {
20161
20399
  writeTaskScheduleState(db, {
20162
20400
  projectPath: projectIdentity,
20163
20401
  task: due.config.task,
20164
- lastRunAt: status === "completed" ? finishedAt : readLastRunAt(db, projectIdentity, due.config.task),
20402
+ lastRunAt: status === "completed" ? startedAt ?? finishedAt : readLastRunAt(db, projectIdentity, due.config.task),
20165
20403
  nextDueAt: nextDueAtMs(due.config.schedule, finishedAt, due.scheduledAt),
20166
20404
  schedule: due.config.schedule,
20167
20405
  lastStatus: status,
20168
20406
  lastError: error,
20169
20407
  retryCount: 0,
20408
+ taskStateJson: schedulePatch?.taskStateJson,
20170
20409
  retrospectiveWatermarkMs: schedulePatch?.retrospectiveWatermarkMs
20171
20410
  });
20172
20411
  }
@@ -20245,6 +20484,7 @@ async function runDomainGroup(deps, group, cb) {
20245
20484
  }
20246
20485
  }
20247
20486
  let outcome;
20487
+ const startedAt = Date.now();
20248
20488
  try {
20249
20489
  outcome = await executor(due.config, {
20250
20490
  db,
@@ -20258,8 +20498,8 @@ async function runDomainGroup(deps, group, cb) {
20258
20498
  }
20259
20499
  const finishedAt = Date.now();
20260
20500
  if (outcome.status === "completed") {
20261
- advanceAfterRun(db, projectIdentity, due, finishedAt, "completed", null, outcome.schedulePatch);
20262
- cb?.onRan?.(due.config.task, outcome.detail);
20501
+ advanceAfterRun(db, projectIdentity, due, finishedAt, "completed", null, outcome.schedulePatch, startedAt);
20502
+ cb?.onRan?.(due.config.task, outcome.detail, outcome.backlog);
20263
20503
  } else if (outcome.transient) {
20264
20504
  recordTransientFailure(db, projectIdentity, due, finishedAt, outcome.error ?? null);
20265
20505
  cb?.onFailed?.(due.config.task, outcome.failureDetail ?? outcome.error);
@@ -20326,6 +20566,7 @@ async function runManualDream(deps) {
20326
20566
  return result;
20327
20567
  }
20328
20568
  const groups = new Map;
20569
+ const runLocalBacklogs = {};
20329
20570
  for (const d of gated) {
20330
20571
  const kind = leaseKindFor(d.config.task);
20331
20572
  const arr = groups.get(kind) ?? [];
@@ -20335,10 +20576,12 @@ async function runManualDream(deps) {
20335
20576
  await Promise.all([...groups.values()].map((group) => runDomainGroup({ ...deps, executor: deps.executor }, group, {
20336
20577
  forceGate,
20337
20578
  leaseWaitMs: MANUAL_RUN_LEASE_WAIT_MS,
20338
- onRan: (t, detail) => {
20579
+ onRan: (t, detail, backlog) => {
20339
20580
  result.ran.push(t);
20340
20581
  if (detail)
20341
20582
  result.details?.push(detail);
20583
+ if (backlog)
20584
+ runLocalBacklogs[t] = backlog;
20342
20585
  },
20343
20586
  onFailed: (task, error) => {
20344
20587
  result.failed.push(task);
@@ -20347,7 +20590,10 @@ async function runManualDream(deps) {
20347
20590
  },
20348
20591
  onBusy: (t) => result.deferredBusy.push(t)
20349
20592
  })));
20350
- result.backlogAfter = getDreamTaskBacklogs(deps.db, deps.projectIdentity, selectedTaskNames);
20593
+ result.backlogAfter = {
20594
+ ...getDreamTaskBacklogs(deps.db, deps.projectIdentity, selectedTaskNames),
20595
+ ...runLocalBacklogs
20596
+ };
20351
20597
  return result;
20352
20598
  }
20353
20599
  async function runDueTasksForProject(deps) {
@@ -21380,6 +21626,8 @@ async function ensureProjectRegisteredFromOpenCodeDirectory(directory, db) {
21380
21626
  registerProjectEmbedding(db, projectIdentity, routing.primary, features, directory);
21381
21627
  if (routing.shadow) {
21382
21628
  registerProjectShadowEmbedding(db, projectIdentity, routing.shadow, directory);
21629
+ } else {
21630
+ unregisterProjectShadowEmbedding(projectIdentity);
21383
21631
  }
21384
21632
  }
21385
21633
 
@@ -22081,6 +22329,56 @@ function createTagger() {
22081
22329
  };
22082
22330
  }
22083
22331
 
22332
+ // src/plugin/memory-id-translation.ts
22333
+ function unmappedMemoryIdMessage(id) {
22334
+ return `Error: memory id ${id} has no module mapping yet — it was written seconds ago or the mirror is behind; retry or use the id shown in <project-memory>.`;
22335
+ }
22336
+ function translateHostMemoryIds(db, hostIds) {
22337
+ const moduleIds = [];
22338
+ for (const hostId of hostIds) {
22339
+ const identity = moduleMemoryIdentityForHostId(db, hostId);
22340
+ if (!identity)
22341
+ return { error: unmappedMemoryIdMessage(hostId) };
22342
+ moduleIds.push(identity.moduleRowId);
22343
+ }
22344
+ return { moduleIds };
22345
+ }
22346
+ function moduleMemoryOperation(response, depth = 0) {
22347
+ if (depth > 4 || response === null || typeof response !== "object")
22348
+ return null;
22349
+ if (Array.isArray(response)) {
22350
+ for (const item of response) {
22351
+ const operation = moduleMemoryOperation(item, depth + 1);
22352
+ if (operation)
22353
+ return operation;
22354
+ }
22355
+ return null;
22356
+ }
22357
+ const record = response;
22358
+ if (record.memory_operation !== null && typeof record.memory_operation === "object" && typeof record.memory_operation.action === "string") {
22359
+ return record.memory_operation;
22360
+ }
22361
+ return moduleMemoryOperation(record.result, depth + 1);
22362
+ }
22363
+ function translateModuleMemoryMutationReply(args) {
22364
+ const operation = moduleMemoryOperation(args.response);
22365
+ if (operation?.action === "write" && operation.module_id !== undefined) {
22366
+ const identity = hostMemoryIdentityForModuleId(args.db, args.moduleProject, operation.module_id);
22367
+ const category = operation.category ?? args.requestedCategory;
22368
+ return identity ? `Saved memory [ID: ${identity.contextRowId}] in ${category}.` : `Saved memory in ${category}. Its id will appear in <project-memory> on the next pass.`;
22369
+ }
22370
+ if (operation?.action === "merge" && operation.canonical_module_id !== undefined) {
22371
+ const canonical = hostMemoryIdentityForModuleId(args.db, args.moduleProject, operation.canonical_module_id);
22372
+ const superseded = (operation.superseded_module_ids ?? []).map((moduleId) => hostMemoryIdentityForModuleId(args.db, args.moduleProject, moduleId)?.contextRowId).filter((id) => id !== undefined);
22373
+ const category = operation.category ?? args.requestedCategory;
22374
+ if (canonical) {
22375
+ return `Merged memories [${args.requestedHostIds.join(", ")}] into canonical memory [ID: ${canonical.contextRowId}] in ${category}; superseded [${superseded.join(", ")}].`;
22376
+ }
22377
+ return `Merged memories [${args.requestedHostIds.join(", ")}] into a canonical memory in ${category}. Its id will appear in <project-memory> on the next pass.`;
22378
+ }
22379
+ return null;
22380
+ }
22381
+
22084
22382
  // src/plugin/rpc-handlers.ts
22085
22383
  import { randomUUID as randomUUID4 } from "node:crypto";
22086
22384
  import { once } from "node:events";
@@ -26971,11 +27269,35 @@ function toFlatModuleWireBody(payload) {
26971
27269
  function moduleWireBodyBytes(payload) {
26972
27270
  return Buffer.byteLength(JSON.stringify(toFlatModuleWireBody(payload)));
26973
27271
  }
26974
- function buildPagedModuleTransformPayloads(body, pageMaxBytes = MODULE_PAGE_MAX_BYTES) {
27272
+ function buildPagedModuleTransformPayloads(body, pageMaxBytes = MODULE_PAGE_MAX_BYTES, forcePageEnvelope = false) {
26975
27273
  const serializedBody = JSON.stringify(body);
26976
27274
  const unpagedBytes = Buffer.byteLength(serializedBody);
26977
- if (unpagedBytes <= pageMaxBytes)
26978
- return [{ page: body, bytes: unpagedBytes }];
27275
+ if (unpagedBytes <= pageMaxBytes) {
27276
+ if (!forcePageEnvelope)
27277
+ return [{ page: body, bytes: unpagedBytes }];
27278
+ const pageContent = Object.fromEntries([
27279
+ "input",
27280
+ "messages",
27281
+ "native_messages",
27282
+ "ts_output",
27283
+ "ts_ck_messages",
27284
+ "normalizations",
27285
+ "tool_input_key_orders"
27286
+ ].filter((field) => {
27287
+ const value = body[field];
27288
+ return Array.isArray(value) || value !== null && typeof value === "object" && field === "tool_input_key_orders";
27289
+ }).map((field) => [field, body[field]]));
27290
+ const page = {
27291
+ transform_page_id: crypto2.createHash("sha256").update(serializedBody).digest("hex"),
27292
+ transform_generation: body.shadow_generation ?? 0,
27293
+ transform_page_index: 0,
27294
+ transform_page_total: 1,
27295
+ transform_page_complete: true,
27296
+ transform_page_digest: transformPageDigest(pageContent),
27297
+ ...body
27298
+ };
27299
+ return [{ page, bytes: Buffer.byteLength(JSON.stringify(page)) }];
27300
+ }
26979
27301
  const arrayFields = [
26980
27302
  "input",
26981
27303
  "messages",
@@ -27625,36 +27947,43 @@ function canonicalOrdinalForMessageId(args) {
27625
27947
  args.state.idOrdinalMemo.set(args.messageId, canonical);
27626
27948
  return canonical;
27627
27949
  }
27628
- function serializeCompartment(args) {
27950
+ var boundaryDiagnosticObserverForTest = null;
27951
+ function logBoundaryDiagnostic(sessionId, message) {
27952
+ sessionLog(sessionId, message);
27953
+ boundaryDiagnosticObserverForTest?.(message);
27954
+ }
27955
+ function readCompartmentBoundaryResolution(args) {
27629
27956
  const startRaw = args.readRawById(args.compartment.startMessageId);
27630
27957
  const endRaw = args.readRawById(args.compartment.endMessageId);
27631
- const startOrdinal = canonicalOrdinalForMessageId({
27632
- sessionId: args.sessionId,
27633
- raw: startRaw,
27634
- messageId: args.compartment.startMessageId,
27635
- generation: args.state.moduleGeneration,
27636
- state: args.state
27637
- });
27638
- const endOrdinal = canonicalOrdinalForMessageId({
27639
- sessionId: args.sessionId,
27640
- raw: endRaw,
27641
- messageId: args.compartment.endMessageId,
27642
- generation: args.state.moduleGeneration,
27643
- state: args.state
27644
- });
27645
- if (startOrdinal === "mismatch" || endOrdinal === "mismatch")
27646
- return "mismatch";
27647
- if (startOrdinal === null || endOrdinal === null)
27648
- return null;
27649
- const startCreatedAt = startRaw?.createdAt;
27650
- const endCreatedAt = endRaw?.createdAt;
27958
+ return {
27959
+ startRaw,
27960
+ endRaw,
27961
+ startOrdinal: canonicalOrdinalForMessageId({
27962
+ sessionId: args.sessionId,
27963
+ raw: startRaw,
27964
+ messageId: args.compartment.startMessageId,
27965
+ generation: args.state.moduleGeneration,
27966
+ state: args.state
27967
+ }),
27968
+ endOrdinal: canonicalOrdinalForMessageId({
27969
+ sessionId: args.sessionId,
27970
+ raw: endRaw,
27971
+ messageId: args.compartment.endMessageId,
27972
+ generation: args.state.moduleGeneration,
27973
+ state: args.state
27974
+ })
27975
+ };
27976
+ }
27977
+ function serializeCompartment(args) {
27978
+ const startCreatedAt = args.startRaw?.createdAt;
27979
+ const endCreatedAt = args.endRaw?.createdAt;
27651
27980
  const dateRange = typeof startCreatedAt === "number" && typeof endCreatedAt === "number" ? { start_date: formatDate(startCreatedAt), end_date: formatDate(endCreatedAt) } : {};
27652
27981
  return {
27653
27982
  sequence: args.compartment.sequence,
27654
- start_message: startOrdinal,
27655
- end_message: endOrdinal,
27656
- start_message_id: flatBlockIdForRawMessage(args.compartment.startMessageId, startRaw, "start"),
27657
- end_message_id: flatBlockIdForRawMessage(args.compartment.endMessageId, endRaw, "end"),
27983
+ start_message: args.startOrdinal,
27984
+ end_message: args.endOrdinal,
27985
+ start_message_id: flatBlockIdForRawMessage(args.compartment.startMessageId, args.startRaw, "start"),
27986
+ end_message_id: flatBlockIdForRawMessage(args.compartment.endMessageId, args.endRaw, "end"),
27658
27987
  ...dateRange,
27659
27988
  title: args.compartment.title,
27660
27989
  content: args.compartment.content,
@@ -27668,6 +27997,15 @@ function serializeCompartment(args) {
27668
27997
  created_at: args.compartment.createdAt
27669
27998
  };
27670
27999
  }
28000
+ function adjacentCompartmentBoundaryId(args) {
28001
+ const previous = args.direction === "previous";
28002
+ const row = args.db.prepare(`SELECT ${previous ? "end_message_id" : "start_message_id"} AS message_id
28003
+ FROM compartments
28004
+ WHERE session_id = ? AND sequence ${previous ? "<" : ">"} ?
28005
+ ORDER BY sequence ${previous ? "DESC" : "ASC"}
28006
+ LIMIT 1`).get(args.sessionId, args.sequence);
28007
+ return typeof row?.message_id === "string" && row.message_id.length > 0 ? row.message_id : null;
28008
+ }
27671
28009
  function seedBoundaryFromSerializedCompartments(compartments) {
27672
28010
  const serialized = compartments.filter(isRecord).filter((compartment) => typeof compartment.sequence === "number" && typeof compartment.end_message_id === "string");
27673
28011
  serialized.sort((left, right) => left.sequence - right.sequence);
@@ -28140,21 +28478,102 @@ async function buildModuleStateSyncPayload(args) {
28140
28478
  const compartments = [];
28141
28479
  let serializedCount = 0;
28142
28480
  const compartmentsToSerialize = compartmentsChanged ? args.force ? inventory ? readCompartmentsAfterSequence(args.pass.db, args.pass.sessionId, inventory.maxCompartmentSequence) : getCompartments(args.pass.db, args.pass.sessionId) : readCompartmentsAfterSequence(args.pass.db, args.pass.sessionId, acked.compartment_sequence) : [];
28143
- for (const compartment of compartmentsToSerialize) {
28144
- args.options?.beforeSerializeCompartment?.();
28145
- if (args.options?.shouldAbortSeed?.())
28146
- return "seed_budget";
28147
- const serialized = serializeCompartment({
28481
+ const boundaryResolutions = new Map;
28482
+ const resolveBoundary = (compartment) => {
28483
+ const cached = boundaryResolutions.get(compartment.sequence);
28484
+ if (cached)
28485
+ return cached;
28486
+ const resolved = readCompartmentBoundaryResolution({
28148
28487
  compartment,
28149
28488
  sessionId: args.pass.sessionId,
28150
28489
  readRawById,
28151
28490
  state: args.state
28152
28491
  });
28153
- if (serialized === "mismatch")
28492
+ boundaryResolutions.set(compartment.sequence, resolved);
28493
+ return resolved;
28494
+ };
28495
+ const canonicalOrdinalForAdjacentId = (messageId) => {
28496
+ if (!messageId)
28497
+ return null;
28498
+ const raw = readRawById(messageId);
28499
+ return canonicalOrdinalForMessageId({
28500
+ sessionId: args.pass.sessionId,
28501
+ raw,
28502
+ messageId,
28503
+ generation: args.state.moduleGeneration,
28504
+ state: args.state
28505
+ });
28506
+ };
28507
+ let firstRawOrdinal;
28508
+ for (const [index, compartment] of compartmentsToSerialize.entries()) {
28509
+ args.options?.beforeSerializeCompartment?.();
28510
+ if (args.options?.shouldAbortSeed?.())
28511
+ return "seed_budget";
28512
+ const boundary = resolveBoundary(compartment);
28513
+ if (boundary.startOrdinal === "mismatch" || boundary.endOrdinal === "mismatch") {
28154
28514
  return "mismatch";
28155
- if (serialized === null)
28156
- return "unresolved";
28157
- compartments.push(serialized);
28515
+ }
28516
+ const startMissing = boundary.startOrdinal === null;
28517
+ const endMissing = boundary.endOrdinal === null;
28518
+ if (startMissing && endMissing) {
28519
+ logBoundaryDiagnostic(args.pass.sessionId, `state-sync compartment skipped session=${args.pass.sessionId} sequence=${compartment.sequence} missing_start_id=${compartment.startMessageId} missing_end_id=${compartment.endMessageId} method=both_boundaries_dangling`);
28520
+ continue;
28521
+ }
28522
+ let startOrdinal = boundary.startOrdinal;
28523
+ if (startOrdinal === null) {
28524
+ const previous = compartmentsToSerialize[index - 1];
28525
+ const previousBoundaryId = previous ? previous.endMessageId : adjacentCompartmentBoundaryId({
28526
+ db: args.pass.db,
28527
+ sessionId: args.pass.sessionId,
28528
+ sequence: compartment.sequence,
28529
+ direction: "previous"
28530
+ });
28531
+ const previousEnd = previous ? resolveBoundary(previous).endOrdinal : canonicalOrdinalForAdjacentId(previousBoundaryId);
28532
+ if (previousEnd === "mismatch")
28533
+ return "mismatch";
28534
+ if (typeof previousEnd === "number") {
28535
+ startOrdinal = previousEnd + 1;
28536
+ logBoundaryDiagnostic(args.pass.sessionId, `state-sync boundary repaired session=${args.pass.sessionId} sequence=${compartment.sequence} side=start missing_id=${compartment.startMessageId} resolved_ordinal=${startOrdinal} method=previous_compartment_end_plus_one`);
28537
+ } else if (previousBoundaryId === null) {
28538
+ if (firstRawOrdinal === undefined) {
28539
+ const ordinals = [
28540
+ ...readRawSessionMessageIdOrdinals(args.pass.sessionId).values()
28541
+ ].filter((ordinal) => ordinal >= 1).sort((left, right) => left - right);
28542
+ firstRawOrdinal = ordinals[0] ?? null;
28543
+ }
28544
+ startOrdinal = firstRawOrdinal;
28545
+ if (startOrdinal !== null) {
28546
+ logBoundaryDiagnostic(args.pass.sessionId, `state-sync boundary repaired session=${args.pass.sessionId} sequence=${compartment.sequence} side=start missing_id=${compartment.startMessageId} resolved_ordinal=${startOrdinal} method=raw_store_first_ordinal`);
28547
+ }
28548
+ }
28549
+ }
28550
+ let endOrdinal = boundary.endOrdinal;
28551
+ if (endOrdinal === null) {
28552
+ const next = compartmentsToSerialize[index + 1];
28553
+ const nextStart = next ? resolveBoundary(next).startOrdinal : canonicalOrdinalForAdjacentId(adjacentCompartmentBoundaryId({
28554
+ db: args.pass.db,
28555
+ sessionId: args.pass.sessionId,
28556
+ sequence: compartment.sequence,
28557
+ direction: "next"
28558
+ }));
28559
+ if (nextStart === "mismatch")
28560
+ return "mismatch";
28561
+ if (typeof nextStart === "number") {
28562
+ endOrdinal = nextStart - 1;
28563
+ logBoundaryDiagnostic(args.pass.sessionId, `state-sync boundary repaired session=${args.pass.sessionId} sequence=${compartment.sequence} side=end missing_id=${compartment.endMessageId} resolved_ordinal=${endOrdinal} method=next_compartment_start_minus_one`);
28564
+ }
28565
+ }
28566
+ if (startOrdinal === null || endOrdinal === null || startOrdinal > endOrdinal) {
28567
+ logBoundaryDiagnostic(args.pass.sessionId, `state-sync compartment skipped session=${args.pass.sessionId} sequence=${compartment.sequence} missing_start_id=${startMissing ? compartment.startMessageId : "none"} missing_end_id=${endMissing ? compartment.endMessageId : "none"} method=${startOrdinal !== null && endOrdinal !== null ? "invalid_repaired_range" : "no_adjacent_canonical_boundary"}`);
28568
+ continue;
28569
+ }
28570
+ compartments.push(serializeCompartment({
28571
+ compartment,
28572
+ startRaw: boundary.startRaw,
28573
+ endRaw: boundary.endRaw,
28574
+ startOrdinal,
28575
+ endOrdinal
28576
+ }));
28158
28577
  serializedCount += 1;
28159
28578
  const yieldEvery = Math.max(1, args.options?.yieldEveryCompartments ?? 10);
28160
28579
  if (serializedCount % yieldEvery === 0) {
@@ -29517,7 +29936,7 @@ function registerRpcHandlers(rpcServer, args) {
29517
29936
  const buildManagedCtx = async (db) => {
29518
29937
  const { deriveHistorianChunkTokens, resolveHistorianContextLimit } = await import("./derive-budgets-v1fkhjjr.js");
29519
29938
  const { resolveHistorianModel } = await import("./model-resolution-dxatzjde.js");
29520
- const { userMemoryCollectionEnabled } = await import("./task-config-ys0arma5.js");
29939
+ const { userMemoryCollectionEnabled } = await import("./task-config-cv8k9y8r.js");
29521
29940
  const DEFAULT_HISTORIAN_TIMEOUT_MS = 600000;
29522
29941
  const historianModel = resolveHistorianModel(config, "opencode");
29523
29942
  return {
@@ -29548,8 +29967,8 @@ function registerRpcHandlers(rpcServer, args) {
29548
29967
  const db = getDb();
29549
29968
  if (!db)
29550
29969
  return { ok: false, error: "db unavailable" };
29551
- const { runManagedRecomp } = await import("./recomp-orchestrator-c4sa354a.js");
29552
- const { sendIgnoredMessage } = await import("./send-session-notification-xnypkxpx.js");
29970
+ const { runManagedRecomp } = await import("./recomp-orchestrator-0xx1364v.js");
29971
+ const { sendIgnoredMessage } = await import("./send-session-notification-jbxknvar.js");
29553
29972
  log(`[rpc] recomp requested for session ${sessionId}`);
29554
29973
  const ctx = await buildManagedCtx(db);
29555
29974
  runManagedRecomp(ctx, sessionId).then((message) => {
@@ -29567,8 +29986,8 @@ function registerRpcHandlers(rpcServer, args) {
29567
29986
  const db = getDb();
29568
29987
  if (!db)
29569
29988
  return { ok: false, error: "db unavailable" };
29570
- const { runManagedUpgrade } = await import("./recomp-orchestrator-c4sa354a.js");
29571
- const { sendIgnoredMessage } = await import("./send-session-notification-xnypkxpx.js");
29989
+ const { runManagedUpgrade } = await import("./recomp-orchestrator-0xx1364v.js");
29990
+ const { sendIgnoredMessage } = await import("./send-session-notification-jbxknvar.js");
29572
29991
  log(`[rpc] session-upgrade requested for session ${sessionId}`);
29573
29992
  const ctx = await buildManagedCtx(db);
29574
29993
  runManagedUpgrade(ctx, sessionId).then((message) => {
@@ -29584,7 +30003,7 @@ function registerRpcHandlers(rpcServer, args) {
29584
30003
  if (!db)
29585
30004
  return { ok: false, error: "db unavailable" };
29586
30005
  try {
29587
- const { updateSessionMeta } = await import("./storage-meta-session-x3j0q9qy.js");
30006
+ const { updateSessionMeta } = await import("./storage-meta-session-z818pn1e.js");
29588
30007
  updateSessionMeta(db, sessionId, { upgradeRemindedAt: Date.now() });
29589
30008
  return { ok: true };
29590
30009
  } catch (error) {
@@ -30100,6 +30519,31 @@ function releaseClaimWithoutDelivery(db, sessionId, claimToken) {
30100
30519
  sessionLog(sessionId, "channel2 terminal-run claim release failed; lease will heal:", error);
30101
30520
  }
30102
30521
  }
30522
+ async function deliverSyntheticUserMessage(sessionId, deps) {
30523
+ const promptContext = await resolvePromptContext2(deps.client, sessionId);
30524
+ const body = {
30525
+ noReply: false,
30526
+ parts: [{ type: "text", text: deps.text, synthetic: true }]
30527
+ };
30528
+ if (promptContext?.agent)
30529
+ body.agent = promptContext.agent;
30530
+ if (promptContext?.model) {
30531
+ body.model = {
30532
+ providerID: promptContext.model.providerID,
30533
+ modelID: promptContext.model.modelID
30534
+ };
30535
+ }
30536
+ if (promptContext?.variant)
30537
+ body.variant = promptContext.variant;
30538
+ if (deps.beforeSend && !deps.beforeSend())
30539
+ return false;
30540
+ const session = deps.client.session;
30541
+ if (typeof session?.promptAsync !== "function") {
30542
+ throw new Error("client has no session.promptAsync");
30543
+ }
30544
+ await session.promptAsync({ path: { id: sessionId }, body });
30545
+ return true;
30546
+ }
30103
30547
  async function maybeDeliverChannel2(sessionId, deps) {
30104
30548
  let state;
30105
30549
  try {
@@ -30138,36 +30582,25 @@ async function maybeDeliverChannel2(sessionId, deps) {
30138
30582
  return false;
30139
30583
  }
30140
30584
  try {
30141
- const promptContext = await resolvePromptContext2(client, sessionId);
30142
30585
  const reminder = deps.directiveText ?? buildChannel2Reminder(effectiveU, reclaimableToolOutputCount(deps.baseline?.baselineParts ?? []), deps.oldestReclaimableToolTags);
30143
- const body = {
30144
- noReply: false,
30145
- parts: [{ type: "text", text: reminder, synthetic: true }]
30146
- };
30147
- if (promptContext?.agent)
30148
- body.agent = promptContext.agent;
30149
- if (promptContext?.model) {
30150
- body.model = {
30151
- providerID: promptContext.model.providerID,
30152
- modelID: promptContext.model.modelID
30153
- };
30154
- }
30155
- if (promptContext?.variant)
30156
- body.variant = promptContext.variant;
30157
- const session = client.session;
30158
- if (typeof session?.promptAsync !== "function") {
30159
- throw new Error("client has no session.promptAsync");
30160
- }
30161
- const claim = getChannel2NudgeClaim(deps.db, sessionId);
30162
- if (claim.state !== "claimed" || claim.claimToken !== claimToken) {
30163
- sessionLog(sessionId, `channel2 ceiling nudge delivery skipped: claim no longer owned before send (state=${claim.state || "empty"})`);
30164
- return false;
30165
- }
30166
- if (!subagentRunIsActive(deps, sessionId)) {
30167
- releaseClaimWithoutDelivery(deps.db, sessionId, claimToken);
30586
+ const sent = await deliverSyntheticUserMessage(sessionId, {
30587
+ client,
30588
+ text: reminder,
30589
+ beforeSend: () => {
30590
+ const claim = getChannel2NudgeClaim(deps.db, sessionId);
30591
+ if (claim.state !== "claimed" || claim.claimToken !== claimToken) {
30592
+ sessionLog(sessionId, `channel2 ceiling nudge delivery skipped: claim no longer owned before send (state=${claim.state || "empty"})`);
30593
+ return false;
30594
+ }
30595
+ if (!subagentRunIsActive(deps, sessionId)) {
30596
+ releaseClaimWithoutDelivery(deps.db, sessionId, claimToken);
30597
+ return false;
30598
+ }
30599
+ return true;
30600
+ }
30601
+ });
30602
+ if (!sent)
30168
30603
  return false;
30169
- }
30170
- await session.promptAsync({ path: { id: sessionId }, body });
30171
30604
  } catch (error) {
30172
30605
  try {
30173
30606
  const restored = casChannel2NudgeClaim(deps.db, sessionId, "pending", claimToken);
@@ -30203,7 +30636,7 @@ function canConsumeDeferredOnThisPass(args) {
30203
30636
  return args.schedulerDecision === "execute" || args.contextPercentage >= (args.forceMaterializationPercentage ?? escalationBands(65).forceMaterializationPercentage);
30204
30637
  }
30205
30638
  function hasReclaimRide(signals) {
30206
- return signals.hardFold || signals.force || signals.explicitFlush || signals.publishedHistory || signals.agentDrop;
30639
+ return signals.hardFold || signals.force || signals.explicitFlush || signals.publishedHistory;
30207
30640
  }
30208
30641
 
30209
30642
  // src/hooks/magic-context/caveman-cleanup.ts
@@ -30325,7 +30758,7 @@ function replayCavemanCompression(sessionId, db, targets, tags) {
30325
30758
  }
30326
30759
 
30327
30760
  // src/hooks/magic-context/compaction-off-transition.ts
30328
- import { existsSync as existsSync11 } from "node:fs";
30761
+ import { existsSync as existsSync12 } from "node:fs";
30329
30762
  var loggedUnverifiedMarkerCleanupRetry = false;
30330
30763
  var COMPACTION_OFF_FLIP_NOTICE = [
30331
30764
  "## Magic Context — compaction-off mode is now active",
@@ -30360,7 +30793,7 @@ function clearCachedM0Baseline(db, sessionId) {
30360
30793
  return (result.changes ?? 0) > 0;
30361
30794
  }
30362
30795
  function cleanupOffMarkers(sessionId) {
30363
- if (!existsSync11(getOpenCodeDbPath())) {
30796
+ if (!existsSync12(getOpenCodeDbPath())) {
30364
30797
  return { verified: true, removedLineages: 0, removedRows: 0, retainedLineages: 0 };
30365
30798
  }
30366
30799
  return removeMcOwnedCompactionMarkers(sessionId, MARKER_SUMMARY_TEXT);
@@ -31297,7 +31730,7 @@ function createPassOutcome() {
31297
31730
  }
31298
31731
 
31299
31732
  // src/hooks/magic-context/rust-mode-transform.ts
31300
- import { createHash as createHash12 } from "node:crypto";
31733
+ import { createHash as createHash12, randomUUID as randomUUID6 } from "node:crypto";
31301
31734
 
31302
31735
  // src/hooks/magic-context/raw-fallback-context-limit.ts
31303
31736
  class RawFallbackContextLimitError extends Error {
@@ -33813,7 +34246,8 @@ async function runPostTransformPhase(args) {
33813
34246
  const firstRenderBust = m0M1EnabledForFold && !completeCachedPrefixAvailable;
33814
34247
  let foldExecutedThisPass = false;
33815
34248
  let publishedM1RefreshedThisPass = false;
33816
- const softRefreshOpportunity = args.schedulerDecision === "execute";
34249
+ let prefixPreflightFailed = false;
34250
+ const softRefreshOpportunity = args.schedulerDecision === "execute" || deferredMaterialize;
33817
34251
  let m0RematerializedThisPass = false;
33818
34252
  const m0CoverageBeforeFold = args.sessionMeta.cachedM0Bytes === null ? -1 : args.sessionMeta.cachedM0MaxCompartmentSeq;
33819
34253
  let m0MaterializeReason = null;
@@ -33840,6 +34274,7 @@ async function runPostTransformPhase(args) {
33840
34274
  compactionOff
33841
34275
  });
33842
34276
  preparedPrefix = foldResult;
34277
+ prefixPreflightFailed = foldResult.materializationContentionRetryExhausted === true;
33843
34278
  foldExecutedThisPass = foldExecutesThisPass(foldDueDecision.value || softRefreshOpportunity, foldResult.m0RematerializedThisPass);
33844
34279
  publishedM1RefreshedThisPass = !foldResult.materializationContentionRetryExhausted && previousM1 !== args.sessionMeta.cachedM1Bytes?.toString("utf8");
33845
34280
  m0RematerializedThisPass = foldResult.m0RematerializedThisPass;
@@ -33861,29 +34296,29 @@ async function runPostTransformPhase(args) {
33861
34296
  }
33862
34297
  } catch (error) {
33863
34298
  preparedPrefix = cachedPrefixBeforePreflight;
34299
+ prefixPreflightFailed = true;
33864
34300
  args.passOutcome?.record("m0-m1-fold-preexecution-degradation");
33865
34301
  sessionLog(args.sessionId, "transform: m[0] HARD fold pre-execution failed:", getErrorMessage(error));
33866
34302
  }
33867
34303
  sessionLog(args.sessionId, `m[0] HARD fold decision: reason=${foldDueDecision.reason ?? "unknown"} executed=${foldExecutedThisPass}`);
33868
34304
  }
33869
- const publishedWorkDrainAllowed = args.schedulerDecision === "execute" || materializationRequested || forceMaterialization || emergencyDropEligible || foldExecutedThisPass || firstRenderBust;
33870
- const shouldReadPendingOps = !compactionOff && (materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || foldExecutedThisPass || firstRenderBust || compartmentRunning);
34305
+ const shouldReadPendingOps = !compactionOff && (materializationRequested || publishedM1RefreshedThisPass || args.schedulerDecision === "execute" || forceMaterialization || foldExecutedThisPass || firstRenderBust || compartmentRunning);
33871
34306
  const pendingOps = shouldReadPendingOps ? getPendingOps(args.db, args.sessionId) : [];
33872
34307
  const hasPendingUserOps = pendingOps.length > 0;
33873
34308
  const formatPendingOpsDepth = () => {
33874
34309
  const depth = getPendingOpsCount(args.db, args.sessionId);
33875
34310
  return depth === null ? "not loaded (deferred pass)" : String(depth);
33876
34311
  };
33877
- const shouldApplyPendingOps = !compactionOff && (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization || foldExecutedThisPass || firstRenderBust) && publishedWorkDrainAllowed;
33878
34312
  const rideSignals = {
33879
34313
  hardFold: foldExecutedThisPass || firstRenderBust,
33880
- force: forceMaterialization || emergencyDropEligible,
33881
- explicitFlush: isExplicitFlush,
33882
- publishedHistory: publishedM1RefreshedThisPass || !m0M1EnabledForFold && (args.pendingCompartmentInjection?.compartmentCount ?? 0) > 0 && (args.historyRebuiltThisPass || args.rebuiltHistoryFromInitialPrepare || args.canConsumeDeferredLate && args.deferredHistoryWasPendingAtPassStart),
33883
- agentDrop: false
33884
- };
33885
- let shouldRunHeuristics = !compactionOff && hasReclaimRide(rideSignals) && (rideSignals.publishedHistory || materializationRequested || forceMaterialization || foldExecutedThisPass || firstRenderBust || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
33886
- let isCacheBustingPass = !compactionOff && hasReclaimRide(rideSignals);
34314
+ force: emergencyDropEligible && (args.contextUsage.percentage >= 95 || getEmergencyInputSample(args.db, args.sessionId) === 0),
34315
+ explicitFlush: isExplicitFlush || deferredMaterialize && !prefixPreflightFailed,
34316
+ publishedHistory: publishedM1RefreshedThisPass || !m0M1EnabledForFold && (args.pendingCompartmentInjection?.compartmentCount ?? 0) > 0 && (args.historyRebuiltThisPass || args.rebuiltHistoryFromInitialPrepare || args.canConsumeDeferredLate && args.deferredHistoryWasPendingAtPassStart)
34317
+ };
34318
+ const publishedWorkDrainAllowed = !compactionOff && hasReclaimRide(rideSignals);
34319
+ const shouldApplyPendingOps = publishedWorkDrainAllowed;
34320
+ const shouldRunHeuristics = !compactionOff && hasReclaimRide(rideSignals) && (rideSignals.publishedHistory || materializationRequested || forceMaterialization || foldExecutedThisPass || firstRenderBust || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
34321
+ const isCacheBustingPass = publishedWorkDrainAllowed;
33887
34322
  if (isCacheBustingPass && args.client && args.ctxReduceAvailability.callable && !hasLoggedCtxReducePermissionDeny(args.sessionId)) {
33888
34323
  try {
33889
34324
  const denied = await resolveToolPermissionDenied(args.client, args.sessionId, "ctx_reduce", args.activeAgent);
@@ -33909,13 +34344,8 @@ async function runPostTransformPhase(args) {
33909
34344
  if (!shouldApplyPendingOps && args.schedulerDeferReason !== undefined && args.schedulerDeferReason !== null) {
33910
34345
  sessionLog(args.sessionId, `pending ops WILL NOT APPLY — reason=${args.schedulerDeferReason} pendingOps=${formatPendingOpsDepth()} context=${args.contextUsage.percentage.toFixed(1)}%`);
33911
34346
  }
33912
- if (compartmentRunning && hasPendingUserOps) {
33913
- if (publishedWorkDrainAllowed) {
33914
- const bypassReason = forceMaterialization ? `emergency >=${args.forceMaterializationPercentage}%` : "m0 hard fold";
33915
- sessionLog(args.sessionId, `transform: compartment-gate bypass (${bypassReason}) — applying ${pendingOps.length} pending ops while compartment agent runs (${args.contextUsage.percentage.toFixed(1)}%)`);
33916
- } else {
33917
- sessionLog(args.sessionId, "transform: deferring pending ops — compartment agent in progress");
33918
- }
34347
+ if (hasPendingUserOps && !shouldApplyPendingOps) {
34348
+ sessionLog(args.sessionId, `pending ops held — reason=no originating cache-bust opportunity; scheduler=${args.schedulerDecision}, historianRunning=${compartmentRunning}`);
33919
34349
  }
33920
34350
  let explicitMaterializedSuccessfully = false;
33921
34351
  let deferredMaterializedSuccessfully = false;
@@ -33947,9 +34377,6 @@ async function runPostTransformPhase(args) {
33947
34377
  const tApply = performance.now();
33948
34378
  pendingOpsDidMutate = applyPendingOperations(args.sessionId, args.db, args.targets, args.contextUsage.percentage >= 95 ? newestCtxReduceTagNumbers(getTagsBySession(args.db, args.sessionId)) : args.protectedTagIds, undefined, pendingOps, [], new Set, (reduction) => droppedTokenReductions.push(reduction));
33949
34379
  if (pendingOpsDidMutate) {
33950
- rideSignals.agentDrop = true;
33951
- isCacheBustingPass = hasReclaimRide(rideSignals);
33952
- shouldRunHeuristics = isCacheBustingPass;
33953
34380
  droppedCount += pendingOps.length;
33954
34381
  for (const pendingOp of pendingOps) {
33955
34382
  const message = args.targets.get(pendingOp.tagId)?.message;
@@ -34711,6 +35138,8 @@ var RUST_PARK_RETRY_INTERVAL = 5;
34711
35138
  var RUST_EMERGENCY_WALL_PCT = 95;
34712
35139
  var RUST_PARK_PROBE_PRESSURE_BYPASS_PCT = 90;
34713
35140
  var RUST_SEND_TIMEOUT_MS = 15000;
35141
+ var RUST_SILENT_RESEND_AFTER_MS = 1e4;
35142
+ var RUST_HEALTH_PROBE_TIMEOUT_MS = 2000;
34714
35143
  var RUST_LKG_FROZEN_HEALTHY_PASS_LIMIT = 8;
34715
35144
  var RUST_LKG_FROZEN_RAW_TAIL_GROWTH_LIMIT = 16;
34716
35145
  function activeAgentFromMessages(messages) {
@@ -35620,6 +36049,64 @@ function createRustModeTransform(deps, options) {
35620
36049
  clearTimeout(timer);
35621
36050
  }
35622
36051
  };
36052
+ const callTransformWithSilentResend = async (args, attemptTimeoutMs) => {
36053
+ const startedAtMs = Date.now();
36054
+ const deadlineMs = startedAtMs + attemptTimeoutMs;
36055
+ const silentAfterMs = options.silentResendAfterMsForTests ?? RUST_SILENT_RESEND_AFTER_MS;
36056
+ const probeTimeoutMs = options.healthProbeTimeoutMsForTests ?? RUST_HEALTH_PROBE_TIMEOUT_MS;
36057
+ const originalAttemptId = randomUUID6();
36058
+ const originalBody = isRecord4(args.body) ? { ...args.body, attempt_id: originalAttemptId } : args.body;
36059
+ const original = callModule({ ...args, body: originalBody }, attemptTimeoutMs);
36060
+ if (attemptTimeoutMs <= silentAfterMs)
36061
+ return original;
36062
+ let silentTimer;
36063
+ const first = await Promise.race([
36064
+ original.then((response) => ({ kind: "response", response }), (error) => ({ kind: "error", error })),
36065
+ new Promise((resolve) => {
36066
+ silentTimer = setTimeout(() => resolve({ kind: "silent" }), silentAfterMs);
36067
+ })
36068
+ ]);
36069
+ if (first.kind !== "silent")
36070
+ clearTimeout(silentTimer);
36071
+ if (first.kind === "response")
36072
+ return first.response;
36073
+ if (first.kind === "error")
36074
+ throw first.error;
36075
+ const probeBudgetMs = Math.min(probeTimeoutMs, Math.max(0, deadlineMs - Date.now()));
36076
+ if (probeBudgetMs <= 0)
36077
+ return original;
36078
+ try {
36079
+ await callModule({
36080
+ sessionId: args.sessionId,
36081
+ projectRoot: args.projectRoot,
36082
+ method: "session.status",
36083
+ body: {
36084
+ method: "session.status",
36085
+ v: 1,
36086
+ session_id: args.sessionId
36087
+ },
36088
+ bypassSessionLane: true
36089
+ }, probeBudgetMs);
36090
+ } catch {
36091
+ return original;
36092
+ }
36093
+ const remainingMs = Math.max(0, deadlineMs - Date.now());
36094
+ if (remainingMs <= 0)
36095
+ return original;
36096
+ const resendAttemptId = randomUUID6();
36097
+ sessionLog(args.sessionId, `rust silent resend original_attempt=${originalAttemptId} resend_attempt=${resendAttemptId} silent_gap_ms=${Date.now() - startedAtMs}`);
36098
+ const resendBody = isRecord4(args.body) ? {
36099
+ ...args.body,
36100
+ attempt_id: resendAttemptId,
36101
+ original_attempt_id: originalAttemptId,
36102
+ resend: true
36103
+ } : args.body;
36104
+ return callModule({
36105
+ ...args,
36106
+ body: resendBody,
36107
+ bypassSessionLane: true
36108
+ }, remainingMs);
36109
+ };
35623
36110
  const markFailure = (sessionId, state, error) => {
35624
36111
  state.consecutiveFailures = isNonRetryableStateSyncFailure(error) ? Math.max(RUST_FAILURE_PARK_THRESHOLD, state.consecutiveFailures + 1) : state.consecutiveFailures + 1;
35625
36112
  state.failureCount += 1;
@@ -35836,6 +36323,8 @@ function createRustModeTransform(deps, options) {
35836
36323
  } catch {}
35837
36324
  let appliedAt;
35838
36325
  let emergencyFailClosed = false;
36326
+ let providerProvenEmergency = false;
36327
+ let recoveryProjectRoot = options.projectRoot ?? deps.directory ?? "";
35839
36328
  const passUsageSnapshot = loadContextUsage(deps.contextUsageMap, deps.db, sessionId);
35840
36329
  requestInputTokens = Math.max(0, Math.floor(passUsageSnapshot.inputTokens));
35841
36330
  let preflightError;
@@ -35869,11 +36358,15 @@ function createRustModeTransform(deps, options) {
35869
36358
  }
35870
36359
  const transformGeometry = transformGeometryForWire(resolvedWindowGeometry);
35871
36360
  const hasTrustedEmergencyWall = transformGeometry ? transformGeometry.usable_hard > 0 : resolvedContextLimit !== undefined && resolvedContextLimit > 0;
35872
- emergencyFailClosed = isProviderOverflowFailClosedProven(sessionId) || hardWallUsagePercentage(passUsageSnapshot, transformGeometry) >= RUST_EMERGENCY_WALL_PCT && hasTrustedEmergencyWall;
36361
+ const hardWallPercentage = hardWallUsagePercentage(passUsageSnapshot, transformGeometry);
36362
+ const providerOverflowProven = isProviderOverflowFailClosedProven(sessionId);
36363
+ emergencyFailClosed = providerOverflowProven || hardWallPercentage >= RUST_EMERGENCY_WALL_PCT && hasTrustedEmergencyWall;
35873
36364
  if (overflowState) {
35874
36365
  const detectedLimitMatchesModel = overflowState.detectedContextLimitModelKey === null || canonicalModelIdentity(overflowState.detectedContextLimitModelKey) === canonicalModelIdentity(modelKey ?? "");
35875
36366
  const hasProviderProof = overflowState.detectedContextLimit > 0 && detectedLimitMatchesModel || isProviderOverflowReconfirmed(sessionId);
35876
- emergencyFailClosed ||= overflowState.needsEmergencyRecovery && overflowState.emergencyRecoveryOrigin === "provider_overflow" && hasProviderProof;
36367
+ const persistedProviderEmergency = overflowState.needsEmergencyRecovery && overflowState.emergencyRecoveryOrigin === "provider_overflow" && hasProviderProof;
36368
+ emergencyFailClosed ||= persistedProviderEmergency;
36369
+ providerProvenEmergency = hardWallPercentage >= RUST_EMERGENCY_WALL_PCT && (providerOverflowProven || persistedProviderEmergency);
35877
36370
  }
35878
36371
  const serveRawFallback = (cause) => {
35879
36372
  const contextLimit = transformGeometry?.usable_hard ?? resolvedContextLimit ?? (overflowState && overflowState.detectedContextLimit > 0 ? overflowState.detectedContextLimit : undefined);
@@ -36176,6 +36669,7 @@ function createRustModeTransform(deps, options) {
36176
36669
  nowMs: Date.now()
36177
36670
  };
36178
36671
  const projectRoot = options.projectRoot ?? directory;
36672
+ recoveryProjectRoot = projectRoot;
36179
36673
  const authoritySeqAdoption = { used: false };
36180
36674
  const memorySyncRequested = options.memorySyncRequestedSessions?.delete(sessionId) === true;
36181
36675
  const currentStateSyncInputSignature = stateSyncInputSignature({
@@ -36370,7 +36864,7 @@ function createRustModeTransform(deps, options) {
36370
36864
  logStage(sessionId, "wireBuild", wireBuildStartedAt, timings, wireDelta ? `mode=tail_delta input=${encodedInput.length}` : "mode=full");
36371
36865
  const sendTransformSeries = async (payload, detail = "") => {
36372
36866
  const pagingStartedAt = performance.now();
36373
- const pages = buildPagedModuleTransformPayloads(payload, options.modulePageMaxBytes);
36867
+ const pages = buildPagedModuleTransformPayloads(payload, options.modulePageMaxBytes, true);
36374
36868
  timings.paging += performance.now() - pagingStartedAt;
36375
36869
  const seedMessageCount = Array.isArray(payload.input) ? payload.input.length : Array.isArray(payload.messages) ? payload.messages.length : 0;
36376
36870
  const paged = pages.some((entry) => typeof entry.page.transform_page_id === "string");
@@ -36381,7 +36875,7 @@ function createRustModeTransform(deps, options) {
36381
36875
  try {
36382
36876
  const attemptClass = paged ? index === pages.length - 1 ? "transform_series_execute" : "transform_page_upload" : undefined;
36383
36877
  const attemptTimeoutMs = options.moduleTimeoutMs ?? (attemptClass === "transform_series_execute" ? transformColdStartExecuteTimeoutMs(seedMessageCount) : attemptClass === "transform_page_upload" ? TRANSFORM_PAGE_UPLOAD_TIMEOUT_MS : timeoutMs);
36384
- moduleResponse = await callModule({
36878
+ const callArgs = {
36385
36879
  sessionId,
36386
36880
  projectRoot,
36387
36881
  method: "transform",
@@ -36392,7 +36886,8 @@ function createRustModeTransform(deps, options) {
36392
36886
  },
36393
36887
  generationSensitive: paged && index > 0,
36394
36888
  attemptClass
36395
- }, attemptTimeoutMs);
36889
+ };
36890
+ moduleResponse = page.transform_page_complete ? await callTransformWithSilentResend(callArgs, attemptTimeoutMs) : await callModule(callArgs, attemptTimeoutMs);
36396
36891
  } catch (error) {
36397
36892
  if (paged && isTransformPageAttemptMismatch(error)) {
36398
36893
  return {
@@ -36918,6 +37413,21 @@ function createRustModeTransform(deps, options) {
36918
37413
  sessionLog(sessionId, "mc_rust_emergency_refusal before_lkg");
36919
37414
  markFailure(sessionId, state, error);
36920
37415
  finishPass(false, false);
37416
+ const refusedUser = newestUserMessage(messages);
37417
+ const refusedUserMessageId = refusedUser ? messageIdOf(refusedUser) : null;
37418
+ if (refusedUserMessageId) {
37419
+ try {
37420
+ options.onEngineReconnectRefusal?.({
37421
+ sessionId,
37422
+ projectRoot: recoveryProjectRoot,
37423
+ refusedUserMessageId,
37424
+ providerProvenEmergency,
37425
+ compactionOff: deps.compactionOff === true
37426
+ });
37427
+ } catch (recoveryError) {
37428
+ sessionLog(sessionId, "rust refusal recovery failed to arm:", recoveryError);
37429
+ }
37430
+ }
36921
37431
  throw new EmergencyFailClosedError(ENGINE_RECONNECTING_USER_MESSAGE, {
36922
37432
  cause: error
36923
37433
  });
@@ -37414,6 +37924,7 @@ function createTransform(deps) {
37414
37924
  projectRoot: deps.rustModeProjectRoot,
37415
37925
  notifyParked: deps.onRustModeParked,
37416
37926
  onProjectPrepared: deps.onRustModeProjectPrepared,
37927
+ onEngineReconnectRefusal: deps.onRustEngineReconnectRefusal,
37417
37928
  memorySyncRequestedSessions: deps.rustMemorySyncRequestedSessions,
37418
37929
  allowAuthorityProtocolBypassForTests: deps.rustModeAllowAuthorityProtocolBypassForTests
37419
37930
  }) : undefined;
@@ -38832,6 +39343,142 @@ function formatEmbedFailureSummary(embedded, remaining, failure, style = "markdo
38832
39343
  return `${progress} ${renderEmbeddingFailure(failure?.class ?? "empty_result", style)}`;
38833
39344
  }
38834
39345
 
39346
+ // src/hooks/magic-context/rust-refusal-recovery.ts
39347
+ var RUST_REFUSAL_RECOVERY_PROMPT = "Magic Context's engine reconnected. Continue from where you stopped.";
39348
+ var RUST_REFUSAL_RECOVERY_POLL_MS = 2000;
39349
+ var RUST_REFUSAL_RECOVERY_MAX_MS = 5 * 60000;
39350
+ var RUST_REFUSAL_RECOVERY_PROBE_TIMEOUT_MS = 2000;
39351
+ function errorContainsReconnectRefusal(error) {
39352
+ try {
39353
+ return JSON.stringify(error).includes(ENGINE_RECONNECTING_USER_MESSAGE);
39354
+ } catch {
39355
+ return false;
39356
+ }
39357
+ }
39358
+ function createRustRefusalRecovery(options) {
39359
+ const watchers = new Map;
39360
+ const latestRefusalBySession = new Map;
39361
+ const pollIntervalMs = options.pollIntervalMs ?? RUST_REFUSAL_RECOVERY_POLL_MS;
39362
+ const maxDurationMs = options.maxDurationMs ?? RUST_REFUSAL_RECOVERY_MAX_MS;
39363
+ const probeTimeoutMs = options.probeTimeoutMs ?? RUST_REFUSAL_RECOVERY_PROBE_TIMEOUT_MS;
39364
+ const readLatestMessage = options.readLatestMessage ?? latestPersistedMessageForRecovery;
39365
+ const cancel = (sessionId) => {
39366
+ const watcher = watchers.get(sessionId);
39367
+ if (!watcher)
39368
+ return;
39369
+ clearTimeout(watcher.timer);
39370
+ watchers.delete(sessionId);
39371
+ };
39372
+ const schedule = (sessionId, watcher) => {
39373
+ watcher.timer = setTimeout(() => void poll(sessionId, watcher), pollIntervalMs);
39374
+ };
39375
+ const probe = async (watcher, sessionId) => {
39376
+ const controller = new AbortController;
39377
+ const timeoutError = new Error("rust refusal recovery probe timed out");
39378
+ const timer = setTimeout(() => controller.abort(timeoutError), probeTimeoutMs);
39379
+ try {
39380
+ await Promise.race([
39381
+ options.moduleClient.call({
39382
+ sessionId,
39383
+ projectRoot: watcher.projectRoot,
39384
+ method: "session.status",
39385
+ body: { method: "session.status", v: 1, session_id: sessionId },
39386
+ signal: controller.signal,
39387
+ timeoutMs: probeTimeoutMs,
39388
+ bypassSessionLane: true
39389
+ }),
39390
+ new Promise((_resolve, reject) => {
39391
+ controller.signal.addEventListener("abort", () => reject(controller.signal.reason ?? timeoutError), { once: true });
39392
+ })
39393
+ ]);
39394
+ return true;
39395
+ } catch {
39396
+ return false;
39397
+ } finally {
39398
+ clearTimeout(timer);
39399
+ }
39400
+ };
39401
+ const poll = async (sessionId, watcher) => {
39402
+ if (watchers.get(sessionId) !== watcher)
39403
+ return;
39404
+ if (Date.now() - watcher.armedAtMs >= maxDurationMs) {
39405
+ cancel(sessionId);
39406
+ sessionLog(sessionId, "rust refusal recovery expired before the module became healthy");
39407
+ return;
39408
+ }
39409
+ if (!await probe(watcher, sessionId)) {
39410
+ schedule(sessionId, watcher);
39411
+ return;
39412
+ }
39413
+ let latest;
39414
+ try {
39415
+ latest = readLatestMessage(sessionId);
39416
+ } catch (error) {
39417
+ sessionLog(sessionId, "rust refusal recovery could not read persisted history:", error);
39418
+ schedule(sessionId, watcher);
39419
+ return;
39420
+ }
39421
+ const matchingAssistant = latest?.role === "assistant" && (latest.parentID === undefined || latest.parentID === watcher.refusedUserMessageId) ? latest : null;
39422
+ const stillRefusedStep = matchingAssistant !== null && (errorContainsReconnectRefusal(matchingAssistant.error) || matchingAssistant.parentID === watcher.refusedUserMessageId && matchingAssistant.error === undefined && matchingAssistant.completedAt === undefined);
39423
+ if (!stillRefusedStep) {
39424
+ cancel(sessionId);
39425
+ sessionLog(sessionId, "rust refusal recovery skipped because persisted history advanced past the refused step");
39426
+ return;
39427
+ }
39428
+ if (watcher.state !== "pending" || watchers.get(sessionId) !== watcher)
39429
+ return;
39430
+ watcher.state = "claimed";
39431
+ const delivered = await deliverSyntheticUserMessage(sessionId, {
39432
+ client: options.client,
39433
+ text: RUST_REFUSAL_RECOVERY_PROMPT,
39434
+ beforeSend: () => watchers.get(sessionId) === watcher && watcher.state === "claimed"
39435
+ }).catch((error) => {
39436
+ sessionLog(sessionId, "rust refusal recovery synthetic delivery failed:", error);
39437
+ return false;
39438
+ });
39439
+ if (!delivered) {
39440
+ if (watchers.get(sessionId) === watcher) {
39441
+ watcher.state = "pending";
39442
+ schedule(sessionId, watcher);
39443
+ }
39444
+ return;
39445
+ }
39446
+ watchers.delete(sessionId);
39447
+ sessionLog(sessionId, "rust refusal recovery synthetic continue delivered");
39448
+ };
39449
+ return {
39450
+ arm(args) {
39451
+ if (args.compactionOff || args.providerProvenEmergency)
39452
+ return;
39453
+ const refusalKey = `${args.sessionId}:${args.refusedUserMessageId}`;
39454
+ if (latestRefusalBySession.get(args.sessionId) === refusalKey)
39455
+ return;
39456
+ latestRefusalBySession.set(args.sessionId, refusalKey);
39457
+ const current = watchers.get(args.sessionId);
39458
+ if (current)
39459
+ cancel(args.sessionId);
39460
+ const watcher = {
39461
+ refusalKey,
39462
+ refusedUserMessageId: args.refusedUserMessageId,
39463
+ projectRoot: args.projectRoot,
39464
+ armedAtMs: Date.now(),
39465
+ state: "pending"
39466
+ };
39467
+ watchers.set(args.sessionId, watcher);
39468
+ schedule(args.sessionId, watcher);
39469
+ sessionLog(args.sessionId, `rust refusal recovery armed refusal=${refusalKey}`);
39470
+ },
39471
+ cancel,
39472
+ forget(sessionId) {
39473
+ cancel(sessionId);
39474
+ latestRefusalBySession.delete(sessionId);
39475
+ },
39476
+ activeCountForTests() {
39477
+ return watchers.size;
39478
+ }
39479
+ };
39480
+ }
39481
+
38835
39482
  // src/hooks/magic-context/text-complete.ts
38836
39483
  function createTextCompleteHandler() {
38837
39484
  return async (_input, output) => {
@@ -39232,7 +39879,7 @@ function memoryGuidanceBlock(memoryEnabled) {
39232
39879
  ` : "";
39233
39880
  }
39234
39881
  var BASE_INTRO = (memoryEnabled) => `Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
39235
- Use \`ctx_reduce\` to mark spent tagged content as discardable and reclaim space. Marking is NOT an immediate delete — it queues the content, which stays fully visible until space is actually needed (as soon as the next turn if you're already under pressure, much later if not), so mark a tool output as soon as you're done with it rather than hoarding the call for the end of the turn. The newest token-mass window stays protected until displaced. Syntax: "3-5", "1,2,9", or "1-5,8,12-15".
39882
+ Use \`ctx_reduce\` to mark spent tagged content as discardable and reclaim space. Marking QUEUES content for release. It stays fully visible to you until it is actually released, which may be the next turn or many turns later. Mark a tool output as soon as you're done with it rather than hoarding the call for the end of the turn. The newest token-mass window stays protected until displaced. Syntax: "3-5", "1,2,9", or "1-5,8,12-15".
39236
39883
  Do not announce or narrate \`ctx_reduce\` drops — just call the tool silently. Saying "I'll drop these outputs" wastes tokens the user does not care about.
39237
39884
  ${CTX_NOTE_GUIDANCE}
39238
39885
  ${memoryGuidanceBlock(memoryEnabled)}Use \`ctx_search\` to search across project memories, indexed git commits, and this session's full conversation history (including compacted parts) from one query.
@@ -39993,6 +40640,8 @@ function createMagicContextHook(deps) {
39993
40640
  authoritySeed: (args) => transport.authoritySeed(args),
39994
40641
  authorityDrain: (args) => transport.authorityDrain(args),
39995
40642
  mirrorPull: (args) => transport.mirrorPull(args),
40643
+ mirrorMemory: (args) => transport.mirrorMemory(args),
40644
+ memoryIdentityAck: (args) => transport.memoryIdentityAck(args),
39996
40645
  getCompartmentsAfter: async (sessionId, afterSequence) => {
39997
40646
  const response = await transport.call({
39998
40647
  sessionId,
@@ -40023,18 +40672,45 @@ function createMagicContextHook(deps) {
40023
40672
  return client;
40024
40673
  })();
40025
40674
  const rustModeModuleClient = deps.config.transform_mode === "rust" ? authorityRecoveryModuleClient : undefined;
40026
- const syncModuleDomain = async (domain) => {
40675
+ const rustRefusalRecovery = rustModeModuleClient ? createRustRefusalRecovery({
40676
+ moduleClient: rustModeModuleClient,
40677
+ client: deps.client
40678
+ }) : undefined;
40679
+ const syncModuleDomain = async (domain, pageBudget) => {
40027
40680
  if (!rustModeModuleClient?.mirrorPull)
40028
40681
  return;
40029
40682
  await drainMirrorPages({
40030
40683
  db,
40031
40684
  module: rustModeModuleClient,
40032
40685
  domain,
40033
- limit: 1000
40686
+ limit: 1000,
40687
+ pageBudget
40034
40688
  });
40035
40689
  };
40036
40690
  const syncModuleNotes = () => syncModuleDomain("notes");
40037
- const syncModuleMemories = () => syncModuleDomain("memories");
40691
+ const syncModuleMemoryIdentity = async (moduleProject, moduleRowId, projectRoot) => {
40692
+ if (!rustModeModuleClient?.mirrorMemory)
40693
+ return;
40694
+ const { row } = await rustModeModuleClient.mirrorMemory({
40695
+ module_row_id: moduleRowId,
40696
+ projectRoot
40697
+ });
40698
+ if (!row)
40699
+ return;
40700
+ const identity = applyTargetedMemoryMirrorRow({ db, row });
40701
+ if (!identity || !rustModeModuleClient.memoryIdentityAck)
40702
+ return;
40703
+ await rustModeModuleClient.memoryIdentityAck({
40704
+ project: moduleProject,
40705
+ projectRoot,
40706
+ rows: [
40707
+ {
40708
+ module_row_id: moduleRowId,
40709
+ context_row_id: identity.contextRowId
40710
+ }
40711
+ ]
40712
+ });
40713
+ };
40038
40714
  const rustToolBackends = deps.config.transform_mode === "rust" && rustModeModuleClient ? {
40039
40715
  authorityState: async ({ projectPath, projectRoot, sessionId, domain }) => {
40040
40716
  if (!rustModeModuleClient.authorityStatus)
@@ -40075,7 +40751,7 @@ function createMagicContextHook(deps) {
40075
40751
  filter,
40076
40752
  limit,
40077
40753
  offset,
40078
- noteId
40754
+ noteIds
40079
40755
  }) => {
40080
40756
  const response = await rustModeModuleClient.call({
40081
40757
  sessionId,
@@ -40096,13 +40772,13 @@ function createMagicContextHook(deps) {
40096
40772
  filter,
40097
40773
  limit,
40098
40774
  offset,
40099
- note_id: noteId
40775
+ note_ids: noteIds
40100
40776
  }
40101
40777
  }
40102
40778
  });
40103
40779
  await syncModuleNotes();
40104
40780
  if (compileStatus && !moduleNoteResponseIsError(response)) {
40105
- const moduleRowId = action === "write" ? moduleNoteRowId(response) : noteId ?? null;
40781
+ const moduleRowId = action === "write" ? moduleNoteRowId(response) : noteIds?.[0] ?? null;
40106
40782
  if (moduleRowId === null || !applyMirroredNoteCompileFields({
40107
40783
  db,
40108
40784
  moduleProject: memoryProject,
@@ -40131,6 +40807,11 @@ function createMagicContextHook(deps) {
40131
40807
  reason,
40132
40808
  limit
40133
40809
  }) => {
40810
+ const hostIds = ids ?? [];
40811
+ const translatedIds = translateHostMemoryIds(db, hostIds);
40812
+ if ("error" in translatedIds)
40813
+ return translatedIds.error;
40814
+ const moduleIds = translatedIds.moduleIds;
40134
40815
  const response = await rustModeModuleClient.call({
40135
40816
  sessionId,
40136
40817
  projectRoot,
@@ -40142,14 +40823,24 @@ function createMagicContextHook(deps) {
40142
40823
  action,
40143
40824
  content,
40144
40825
  category,
40145
- ids,
40826
+ ids: moduleIds,
40827
+ host_ids: hostIds,
40828
+ memory_id_lane: "host",
40146
40829
  reason,
40147
40830
  limit,
40148
40831
  memory_project: memoryProject
40149
40832
  }
40150
40833
  }
40151
40834
  });
40152
- await syncModuleMemories();
40835
+ const operation = moduleMemoryOperation(response);
40836
+ const newModuleRowId = operation?.action === "write" ? operation.module_id : operation?.action === "merge" ? operation.canonical_module_id : undefined;
40837
+ try {
40838
+ if (newModuleRowId !== undefined) {
40839
+ await syncModuleMemoryIdentity(memoryProject, newModuleRowId, projectRoot);
40840
+ }
40841
+ } catch (error) {
40842
+ log("[magic-context] targeted memory mirror sync failed:", error);
40843
+ }
40153
40844
  if (!moduleNoteResponseIsError(response) && (action === "write" || action === "update" || action === "merge")) {
40154
40845
  (async () => {
40155
40846
  await ensureProjectRegisteredFromOpenCodeDirectory(projectRoot, db);
@@ -40161,7 +40852,13 @@ function createMagicContextHook(deps) {
40161
40852
  log("[magic-context] mirrored memory embedding failed:", error);
40162
40853
  });
40163
40854
  }
40164
- return response;
40855
+ return translateModuleMemoryMutationReply({
40856
+ db,
40857
+ moduleProject: memoryProject,
40858
+ response,
40859
+ requestedHostIds: hostIds,
40860
+ requestedCategory: category
40861
+ }) ?? response;
40165
40862
  },
40166
40863
  noteEvaluationAvailable: (evaluationProjectPath) => getModuleNoteEvaluationBridge(evaluationProjectPath) !== undefined,
40167
40864
  memorySync: (sessionId) => {
@@ -40298,7 +40995,8 @@ function createMagicContextHook(deps) {
40298
40995
  tsAuthorityRecoveryModuleClient: authorityRecoveryModuleClient,
40299
40996
  rustMemorySyncRequestedSessions,
40300
40997
  onRustModeParked: notifyRustModeParked,
40301
- onRustModeProjectPrepared: ensureModuleNoteEvaluationBridge
40998
+ onRustModeProjectPrepared: ensureModuleNoteEvaluationBridge,
40999
+ onRustEngineReconnectRefusal: (args) => rustRefusalRecovery?.arm(args)
40302
41000
  });
40303
41001
  const eventHandler = createEventHandler2({
40304
41002
  contextUsageMap,
@@ -40323,6 +41021,7 @@ function createMagicContextHook(deps) {
40323
41021
  },
40324
41022
  rustSessionCleanup: rustModeModuleClient !== undefined,
40325
41023
  onSessionDeleted: async (sessionId) => {
41024
+ rustRefusalRecovery?.forget(sessionId);
40326
41025
  dropSlot(sessionId, "session-deleted");
40327
41026
  try {
40328
41027
  await transform.clearRustSession(sessionId);
@@ -40965,6 +41664,46 @@ Two recovery modes for finer detail:
40965
41664
  - ctx_expand(message=138) — returns the FULL untruncated content of the message at that ordinal: every text part, and every tool call's complete input + output, read from stored history. This is the cheap way to get back a tool output you dropped with ctx_reduce — the original is still in storage even though the wire shows [dropped §N§]. If the message was deleted from history (session prune/revert), it says so.`;
40966
41665
  var CTX_EXPAND_TOKEN_BUDGET = 15000;
40967
41666
 
41667
+ // src/tools/ctx-expand/mode.ts
41668
+ function isInt(value) {
41669
+ return typeof value === "number" && Number.isInteger(value);
41670
+ }
41671
+ function minOrdinal(domain) {
41672
+ return domain === "non-negative" ? 0 : 1;
41673
+ }
41674
+ function messageError(domain) {
41675
+ return domain === "non-negative" ? "Error: message must be a non-negative integer." : "Error: message must be a positive integer.";
41676
+ }
41677
+ function rangeError(domain) {
41678
+ return domain === "non-negative" ? "Error: provide either message=<ordinal>, or start and end (non-negative integers, start <= end)." : "Error: provide either message=<ordinal>, or start and end (positive integers, start <= end).";
41679
+ }
41680
+ function resolveCtxExpandMode(args, domain) {
41681
+ const min = minOrdinal(domain);
41682
+ const messagePresent = args.message !== undefined && args.message !== null;
41683
+ const message = isInt(args.message) ? args.message : undefined;
41684
+ const start = isInt(args.start) ? args.start : undefined;
41685
+ const end = isInt(args.end) ? args.end : undefined;
41686
+ const messageValid = message !== undefined && message >= min;
41687
+ const rangeValid = start !== undefined && end !== undefined && start >= min && end >= start;
41688
+ const fillerPair = start === 0 && end === 0;
41689
+ const rangeNamed = rangeValid && !fillerPair;
41690
+ if (messageValid && !rangeNamed) {
41691
+ return { kind: "message", message };
41692
+ }
41693
+ if (messagePresent && !messageValid && !rangeNamed) {
41694
+ return { kind: "error", message: messageError(domain) };
41695
+ }
41696
+ if (rangeValid) {
41697
+ return {
41698
+ kind: "range",
41699
+ start,
41700
+ end,
41701
+ verbose: args.verbose === true
41702
+ };
41703
+ }
41704
+ return { kind: "error", message: rangeError(domain) };
41705
+ }
41706
+
40968
41707
  // src/tools/ctx-expand/render.ts
40969
41708
  function isRecord5(value) {
40970
41709
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -41158,27 +41897,26 @@ function createCtxExpandTool(deps) {
41158
41897
  message: "number"
41159
41898
  });
41160
41899
  const sessionId = toolContext.sessionID;
41161
- if (args.message !== undefined) {
41162
- if (typeof args.message !== "number" || !Number.isInteger(args.message) || args.message < 1) {
41163
- return "Error: message must be a positive integer.";
41164
- }
41165
- return renderMessageByOrdinal(sessionId, args.message);
41900
+ const mode = resolveCtxExpandMode(args, "positive");
41901
+ if (mode.kind === "error") {
41902
+ return mode.message;
41166
41903
  }
41167
- if (!args.start || !args.end || !Number.isInteger(args.start) || !Number.isInteger(args.end) || args.start < 1 || args.end < args.start) {
41168
- return "Error: provide either message=<ordinal>, or start and end (positive integers, start <= end).";
41904
+ if (mode.kind === "message") {
41905
+ return renderMessageByOrdinal(sessionId, mode.message);
41169
41906
  }
41907
+ const { start, end, verbose } = mode;
41170
41908
  const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, sessionId);
41171
- if (lastCompartmentEnd >= 0 && args.start > lastCompartmentEnd) {
41172
- return `Range ${args.start}-${args.end} is entirely within the live tail (after the last compacted message ${lastCompartmentEnd}); those messages are already visible in context.`;
41909
+ if (lastCompartmentEnd >= 0 && start > lastCompartmentEnd) {
41910
+ return `Range ${start}-${end} is entirely within the live tail (after the last compacted message ${lastCompartmentEnd}); those messages are already visible in context.`;
41173
41911
  }
41174
- const effectiveEnd = lastCompartmentEnd >= 0 ? Math.min(args.end, lastCompartmentEnd) : args.end;
41175
- if (args.verbose === true) {
41176
- const v = renderVerboseRange(sessionId, args.start, effectiveEnd, CTX_EXPAND_TOKEN_BUDGET);
41912
+ const effectiveEnd = lastCompartmentEnd >= 0 ? Math.min(end, lastCompartmentEnd) : end;
41913
+ if (verbose) {
41914
+ const v = renderVerboseRange(sessionId, start, effectiveEnd, CTX_EXPAND_TOKEN_BUDGET);
41177
41915
  if (!v.text) {
41178
- return `No messages found in range ${args.start}-${effectiveEnd}. The range may be outside this session's history.`;
41916
+ return `No messages found in range ${start}-${effectiveEnd}. The range may be outside this session's history.`;
41179
41917
  }
41180
41918
  const out = [
41181
- `Messages ${args.start}-${v.lastOrdinal} (verbose). Recover any one in full with ctx_expand(message=<ordinal>):`,
41919
+ `Messages ${start}-${v.lastOrdinal} (verbose). Recover any one in full with ctx_expand(message=<ordinal>):`,
41182
41920
  "",
41183
41921
  v.text
41184
41922
  ];
@@ -41188,9 +41926,9 @@ function createCtxExpandTool(deps) {
41188
41926
  return out.join(`
41189
41927
  `);
41190
41928
  }
41191
- const chunk = readSessionChunk(sessionId, CTX_EXPAND_TOKEN_BUDGET, args.start, effectiveEnd + 1);
41929
+ const chunk = readSessionChunk(sessionId, CTX_EXPAND_TOKEN_BUDGET, start, effectiveEnd + 1);
41192
41930
  if (!chunk.text || chunk.messageCount === 0) {
41193
- return `No messages found in range ${args.start}-${args.end}. The range may be outside this session's history.`;
41931
+ return `No messages found in range ${start}-${end}. The range may be outside this session's history.`;
41194
41932
  }
41195
41933
  const lines = [];
41196
41934
  lines.push(`Messages ${chunk.startIndex}-${chunk.endIndex} (${chunk.messageCount} messages, ~${chunk.tokenEstimate} tokens):`);
@@ -41253,7 +41991,7 @@ function isMemoryCategory(value) {
41253
41991
  return MEMORY_CATEGORIES.has(value);
41254
41992
  }
41255
41993
  function normalizeLimit2(limit) {
41256
- if (typeof limit !== "number" || !Number.isFinite(limit)) {
41994
+ if (typeof limit !== "number" || !Number.isFinite(limit) || limit === 0) {
41257
41995
  return DEFAULT_SEARCH_LIMIT;
41258
41996
  }
41259
41997
  return Math.max(1, Math.floor(limit));
@@ -41535,6 +42273,27 @@ function createCtxMemoryTool(deps) {
41535
42273
  return "Error: Could not resolve project identity for memory action.";
41536
42274
  }
41537
42275
  await deps.ensureProjectRegistered?.(toolContext.directory, deps.db);
42276
+ const activeCurateCategory = toolContext.agent === DREAMER_AGENT ? getActiveCurateCategory(deps.db, projectPath) : null;
42277
+ if (activeCurateCategory) {
42278
+ const usesCategory = ["write", "update", "merge", "list"].includes(args.action);
42279
+ const usesIds = ["update", "archive", "merge", "get"].includes(args.action);
42280
+ const usesSuccessor = args.action === "update" || args.action === "archive";
42281
+ const scopeRefusal = getCurateCategoryScopeRefusal({
42282
+ scope: activeCurateCategory,
42283
+ action: args.action,
42284
+ requestedCategory: usesCategory ? args.category : undefined,
42285
+ ids: usesIds ? [
42286
+ ...args.ids ?? [],
42287
+ ...usesSuccessor && Number.isInteger(args.superseded_by) ? [args.superseded_by] : []
42288
+ ] : [],
42289
+ categoryForId: (id) => {
42290
+ const category = getMemoryById(deps.db, id)?.category;
42291
+ return category ? curateCategoryForMemoryCategory(category) : null;
42292
+ }
42293
+ });
42294
+ if (scopeRefusal)
42295
+ return scopeRefusal;
42296
+ }
41538
42297
  const curatePreflight = toolContext.agent === DREAMER_AGENT ? preflightCurateMutation({
41539
42298
  db: deps.db,
41540
42299
  params: args,
@@ -41669,7 +42428,8 @@ function createCtxMemoryTool(deps) {
41669
42428
  if (args.action === "list") {
41670
42429
  const limit = normalizeLimit2(args.limit);
41671
42430
  const category = normalizeCategory(args.category);
41672
- const memories = filterByCategory(getMemoriesByProject(deps.db, projectPath), category).slice(0, limit);
42431
+ const allMemories = getMemoriesByProject(deps.db, projectPath);
42432
+ const memories = (activeCurateCategory ? allMemories.filter((memory) => curateCategoryForMemoryCategory(memory.category) === activeCurateCategory) : filterByCategory(allMemories, category)).slice(0, limit);
41673
42433
  return formatMemoryList(memories);
41674
42434
  }
41675
42435
  if (args.action === "get") {
@@ -41926,7 +42686,7 @@ Use a note when something matters LATER but not in the next few steps: "revisit
41926
42686
  Actions:
41927
42687
  - write: save a note (content). Add surface_condition to make it a smart note (below).
41928
42688
  - read: list notes, newest first. Default: latest active session notes + ready smart notes; page older ones with limit/offset, or inspect other states with filter.
41929
- - update: change a note by note_id. dismiss: retire one by note_id or 1–50 by note_ids; provide exactly one.
42689
+ - update: change one note (note_ids=[N]). dismiss: retire 1–50 notes (note_ids=[...]).
41930
42690
 
41931
42691
  Smart notes: pass surface_condition and the note stays hidden until a background checker confirms the condition — using ONLY externally verifiable signals (GitHub state via gh, files on disk, git history, web pages). It cannot see this conversation, so the condition must be checkable from outside:
41932
42692
  ✓ "When PR #42 in cortexkit/magic-context is merged"
@@ -41939,7 +42699,7 @@ Example: ctx_note(action="write", content="Re-run the perf benchmark once the bo
41939
42699
  import { tool as tool3 } from "@opencode-ai/plugin";
41940
42700
 
41941
42701
  // src/features/magic-context/smart-notes/condition-compiler.ts
41942
- import { existsSync as existsSync12 } from "node:fs";
42702
+ import { existsSync as existsSync13 } from "node:fs";
41943
42703
  import { isAbsolute as isAbsolute4 } from "node:path";
41944
42704
 
41945
42705
  // ../retina-local-fs/src/path-fence.ts
@@ -42196,7 +42956,7 @@ async function compileSurfaceCondition(surfaceCondition, options) {
42196
42956
  dataDirectory: options.dataDirectory,
42197
42957
  cwd: options.projectPath
42198
42958
  });
42199
- return { path: resolved, exists: existsSync12(resolved) };
42959
+ return { path: resolved, exists: existsSync13(resolved) };
42200
42960
  });
42201
42961
  let predicates;
42202
42962
  try {
@@ -42451,7 +43211,7 @@ function formatNoteLine(note) {
42451
43211
  }
42452
43212
  var DISMISS_FOOTER = `
42453
43213
 
42454
- To dismiss a stale note: ctx_note(action="dismiss", note_id=N)`;
43214
+ To dismiss a stale note: ctx_note(action="dismiss", note_ids=[N])`;
42455
43215
  var DEFAULT_READ_LIMIT = 25;
42456
43216
  function paginateNewestFirst(notes, limit, offset) {
42457
43217
  const total = notes.length;
@@ -42566,8 +43326,7 @@ var ctxNoteArgsShape = {
42566
43326
  filter: tool3.schema.enum(["all", "active", "pending", "ready", "dismissed"]).optional().describe("Optional read filter. Defaults to active session notes + ready smart notes. Use 'all' to inspect every status or 'pending' to inspect unsurfaced smart notes."),
42567
43327
  limit: tool3.schema.number().optional().describe("Max notes per section for read, newest first (default: 25)"),
42568
43328
  offset: tool3.schema.number().optional().describe("Skip this many newest notes for read — page older ones (default: 0)"),
42569
- note_id: tool3.schema.number().optional().describe("Note ID (required for 'dismiss' and 'update' actions)."),
42570
- note_ids: tool3.schema.array(tool3.schema.number().int().min(1)).min(1).max(50).optional().describe("One to fifty note ids for 'dismiss' only; do not combine with note_id.")
43329
+ note_ids: tool3.schema.array(tool3.schema.number().int().min(1)).min(1).max(50).optional().describe("Note ids: exactly one for 'update', one to fifty for 'dismiss'. Ignored by 'write' and 'read'.")
42571
43330
  };
42572
43331
  var ctxNoteArgsSchema = tool3.schema.object(ctxNoteArgsShape).passthrough();
42573
43332
  function formatDismissResults(results) {
@@ -42576,9 +43335,10 @@ function formatDismissResults(results) {
42576
43335
  ${results.map((result) => `- Note #${result.noteId}: ${result.outcome}`).join(`
42577
43336
  `)}`;
42578
43337
  }
42579
- function parseDismissNoteIds(value) {
42580
- if (!Array.isArray(value) || value.length < 1 || value.length > 50 || value.some((id) => typeof id !== "number" || !Number.isInteger(id) || id <= 0)) {
42581
- return "Error: 'note_ids' must contain 1 to 50 positive integer ids when action is 'dismiss'.";
43338
+ function parseNoteIds(action, value) {
43339
+ const max = action === "update" ? 1 : 50;
43340
+ if (!Array.isArray(value) || value.length < 1 || value.length > max || value.some((id) => typeof id !== "number" || !Number.isInteger(id) || id <= 0)) {
43341
+ return action === "update" ? "Error: 'note_ids' must contain exactly one positive integer id when action is 'update'." : "Error: 'note_ids' must contain 1 to 50 positive integer ids when action is 'dismiss'.";
42582
43342
  }
42583
43343
  return value;
42584
43344
  }
@@ -42599,22 +43359,13 @@ function createCtxNoteTool(deps) {
42599
43359
  },
42600
43360
  limit: "number",
42601
43361
  offset: "number",
42602
- note_id: "number",
42603
43362
  note_ids: { type: "array", items: "number", maxItems: 50 }
42604
43363
  });
42605
43364
  const sessionId = toolContext.sessionID;
42606
43365
  const action = args.action ?? (args.content?.trim() ? "write" : "read");
42607
- const hasNoteId = args.note_id !== undefined;
42608
- const hasNoteIds = args.note_ids !== undefined;
42609
- if (hasNoteId && hasNoteIds) {
42610
- return "Error: 'note_id' and 'note_ids' cannot be used together; provide one or the other.";
42611
- }
42612
- if (hasNoteIds && action !== "dismiss") {
42613
- return "Error: 'note_ids' is only valid when action is 'dismiss'.";
42614
- }
42615
- const dismissNoteIds = action === "dismiss" && hasNoteIds ? parseDismissNoteIds(args.note_ids) : undefined;
42616
- if (typeof dismissNoteIds === "string")
42617
- return dismissNoteIds;
43366
+ const noteIds = action === "dismiss" || action === "update" ? parseNoteIds(action, args.note_ids) : undefined;
43367
+ if (typeof noteIds === "string")
43368
+ return noteIds;
42618
43369
  const wakePlaneActive = action === "write" && Boolean(args.surface_condition?.trim()) && await wakePlaneStatus() === "present";
42619
43370
  const surfaceCondition = wakePlaneActive ? undefined : args.surface_condition?.trim();
42620
43371
  const projectIdentity = deps.resolveProjectPath?.(toolContext.directory);
@@ -42663,8 +43414,7 @@ function createCtxNoteTool(deps) {
42663
43414
  filter: args.filter,
42664
43415
  limit: args.limit,
42665
43416
  offset: args.offset,
42666
- noteId: args.note_id,
42667
- noteIds: dismissNoteIds
43417
+ noteIds: Array.isArray(noteIds) ? noteIds : undefined
42668
43418
  };
42669
43419
  try {
42670
43420
  const text = moduleNoteText(await rustNote(request), args, action);
@@ -42733,33 +43483,24 @@ wake plane active — create a scheduled wake instead; stored as a plain note.`;
42733
43483
  return `Saved session note #${note.id}.`;
42734
43484
  }
42735
43485
  if (action === "dismiss") {
42736
- if (dismissNoteIds) {
42737
- if (!projectIdentity) {
42738
- return "Error: Could not resolve project identity for note dismiss.";
42739
- }
42740
- return formatDismissResults(dismissNotes(deps.db, dismissNoteIds, {
42741
- projectPath: projectIdentity,
42742
- sessionId
42743
- }));
42744
- }
42745
- const noteId = args.note_id;
42746
- if (typeof noteId !== "number") {
42747
- return "Error: 'note_id' is required when action is 'dismiss'.";
42748
- }
42749
43486
  if (!projectIdentity) {
42750
43487
  return "Error: Could not resolve project identity for note dismiss.";
42751
43488
  }
42752
- const dismissed = dismissNote(deps.db, noteId, {
43489
+ const ids = noteIds;
43490
+ if (ids.length === 1) {
43491
+ const dismissed = dismissNote(deps.db, ids[0], {
43492
+ projectPath: projectIdentity,
43493
+ sessionId
43494
+ });
43495
+ return dismissed ? `Note #${ids[0]} dismissed.` : `Error: Note #${ids[0]} not found in your session/project or already dismissed.`;
43496
+ }
43497
+ return formatDismissResults(dismissNotes(deps.db, ids, {
42753
43498
  projectPath: projectIdentity,
42754
43499
  sessionId
42755
- });
42756
- return dismissed ? `Note #${noteId} dismissed.` : `Error: Note #${noteId} not found in your session/project or already dismissed.`;
43500
+ }));
42757
43501
  }
42758
43502
  if (action === "update") {
42759
- const noteId = args.note_id;
42760
- if (typeof noteId !== "number") {
42761
- return "Error: 'note_id' is required when action is 'update'.";
42762
- }
43503
+ const noteId = noteIds[0];
42763
43504
  const updates = {};
42764
43505
  if (args.content?.trim())
42765
43506
  updates.content = args.content.trim();
@@ -42831,7 +43572,7 @@ function createCtxNoteTools(deps) {
42831
43572
  var CTX_REDUCE_DESCRIPTION = `Mark spent tagged content as discardable to reclaim context space. This is NOT an immediate delete. Use §N§ identifiers visible in the conversation. The \`drop\` param accepts ranges: "3-5", "1,2,9", "1-5,8".
42832
43573
 
42833
43574
  How it works:
42834
- - Marking QUEUES content for release. It stays fully visible to you until context space is actually needed — which may be as soon as the next turn if you are already under pressure, or many turns later if not. So mark spent outputs as soon as you finish with them; don't hoard the call for the end of the turn.
43575
+ - Marking QUEUES content for release. It stays fully visible to you until it is actually released, which may be the next turn or many turns later. Mark spent outputs as soon as you finish with them; don't hoard the call for the end of the turn.
42835
43576
  - The newest tags are protected: marking one just queues it until it ages out of the recent window, so marking recent output is harmless.
42836
43577
  - When content is finally released it becomes a short placeholder, and re-running the tool is the only way to get it back. So mark only what you are genuinely DONE with — the test is "have I extracted what I need from this?", not "is it safe / do I have time before it drops?".
42837
43578
 
@@ -43076,7 +43817,7 @@ var VALID_SOURCES = new Set([
43076
43817
  "note"
43077
43818
  ]);
43078
43819
  function normalizeLimit3(limit) {
43079
- if (typeof limit !== "number" || !Number.isFinite(limit)) {
43820
+ if (typeof limit !== "number" || !Number.isFinite(limit) || limit === 0) {
43080
43821
  return DEFAULT_CTX_SEARCH_LIMIT;
43081
43822
  }
43082
43823
  return Math.max(1, Math.floor(limit));
@@ -43095,7 +43836,7 @@ function normalizeSources(sources) {
43095
43836
  }
43096
43837
  }
43097
43838
  }
43098
- return result;
43839
+ return sources.length === 0 ? undefined : result;
43099
43840
  }
43100
43841
  var ctxSearchArgsShape = {
43101
43842
  query: tool5.schema.string().optional().describe("Search query. Matches against memory content, Primers, git commit messages, and raw user/assistant message text."),
@@ -43322,7 +44063,7 @@ import {
43322
44063
  chmodSync as chmodSync2,
43323
44064
  mkdirSync as mkdirSync4,
43324
44065
  readdirSync,
43325
- readFileSync as readFileSync6,
44066
+ readFileSync as readFileSync7,
43326
44067
  renameSync as renameSync5,
43327
44068
  rmSync as rmSync4,
43328
44069
  unlinkSync as unlinkSync2,
@@ -43459,7 +44200,7 @@ class MagicContextRpcServer {
43459
44200
  for (const entry of readdirSync(this.portDir)) {
43460
44201
  if (!entry.startsWith("port-") || !entry.endsWith(".json"))
43461
44202
  continue;
43462
- const record = parseRpcPortFile(readFileSync6(`${this.portDir}/${entry}`, "utf-8"));
44203
+ const record = parseRpcPortFile(readFileSync7(`${this.portDir}/${entry}`, "utf-8"));
43463
44204
  if (!record || record.pid === process.pid || !isPidAlive(record.pid))
43464
44205
  continue;
43465
44206
  log(`[rpc] another Magic Context RPC server is active for this project (pid ${record.pid}, port ${record.port}); starting separate instance on a new port`);
@@ -43853,7 +44594,240 @@ ${data.output?.output ?? ""}`
43853
44594
  return [...content.length ? [make("assistant", content)] : [], ...results];
43854
44595
  }
43855
44596
 
44597
+ // src/v2/hooks/hidden-child.ts
44598
+ var HIDDEN_HISTORIAN_AGENT = "historian";
44599
+ var HIDDEN_DREAMER_AGENT = "dreamer-classifier";
44600
+ async function registerHiddenChildAgents(agent) {
44601
+ await agent.transform((editor) => {
44602
+ for (const id of [HIDDEN_HISTORIAN_AGENT, HIDDEN_DREAMER_AGENT]) {
44603
+ editor.update(id, (config) => {
44604
+ config.system = "Magic Context hidden completion carrier.";
44605
+ config.description = "Internal Magic Context hidden completion carrier.";
44606
+ config.mode = "primary";
44607
+ config.hidden = true;
44608
+ config.request.settings = {};
44609
+ config.request.headers = {};
44610
+ config.request.body = {};
44611
+ config.permissions = [{ action: "*", resource: "*", effect: "deny" }];
44612
+ });
44613
+ }
44614
+ });
44615
+ }
44616
+ function newestUserText(draft) {
44617
+ const message = draft.messages.at(-1);
44618
+ if (!message || message.role !== undefined && message.role !== "user")
44619
+ return;
44620
+ const content = message.content;
44621
+ if (typeof content === "string" && content.length > 0)
44622
+ return content;
44623
+ const parts = Array.isArray(content) ? content : Array.isArray(message.parts) ? message.parts : [];
44624
+ for (const part of parts) {
44625
+ if (!part || typeof part !== "object")
44626
+ continue;
44627
+ const record = part;
44628
+ if (typeof record.text !== "string" || record.text.length === 0)
44629
+ continue;
44630
+ if (record.type === undefined || record.type === "text" || record.type === "input_text") {
44631
+ return record.text;
44632
+ }
44633
+ }
44634
+ return;
44635
+ }
44636
+ function calibratedParts(attempt) {
44637
+ const parts = attempt.request.body.parts;
44638
+ if (!Array.isArray(parts) || parts.length === 0 || parts.some((part) => !part || typeof part !== "object" || part.type !== "text" || typeof part.text !== "string")) {
44639
+ throw new HiddenCompletionRefusal("hidden_prompt_unrecognized", "Hidden completion accepts text-only calibrated prompts", true);
44640
+ }
44641
+ return parts.map((part) => ({ type: "text", text: part.text }));
44642
+ }
44643
+
44644
+ class HiddenChildHook {
44645
+ childIDs = new Set;
44646
+ attempts = new Map;
44647
+ registerChild(sessionID) {
44648
+ this.childIDs.add(sessionID);
44649
+ }
44650
+ registerAttempt(marker, attempt) {
44651
+ this.registerChild(attempt.childSessionId);
44652
+ this.attempts.set(marker, attempt);
44653
+ }
44654
+ releaseAttempt(marker) {
44655
+ this.attempts.delete(marker);
44656
+ }
44657
+ owns(sessionID) {
44658
+ return this.childIDs.has(sessionID);
44659
+ }
44660
+ apply(draft) {
44661
+ if (!this.owns(draft.sessionID))
44662
+ return false;
44663
+ const raw = newestUserText(draft);
44664
+ const stripped = raw === undefined ? undefined : stripWellFormedLeadingTagPrefix(raw);
44665
+ const attempt = (raw !== undefined ? this.attempts.get(raw) : undefined) ?? (stripped && stripped !== raw ? this.attempts.get(stripped) : undefined);
44666
+ if (!attempt || attempt.childSessionId !== draft.sessionID) {
44667
+ throw new HiddenCompletionRefusal("hidden_prompt_unrecognized", "Refusing an unregistered prompt on a Magic Context hidden-run session", true);
44668
+ }
44669
+ if (attempt.request.signal?.aborted) {
44670
+ throw new Error("Hidden completion prompt aborted");
44671
+ }
44672
+ const system = typeof attempt.request.body.system === "string" ? attempt.request.body.system : attempt.identity.system;
44673
+ const temperature = attempt.request.body.temperature;
44674
+ draft.system = [{ type: "text", text: system }];
44675
+ draft.messages = [{ role: "user", content: calibratedParts(attempt) }];
44676
+ draft.options = {
44677
+ maxOutputTokens: 32768,
44678
+ maxTokens: 32768,
44679
+ ...typeof temperature === "number" && Number.isFinite(temperature) ? { temperature } : {}
44680
+ };
44681
+ draft.tools = {};
44682
+ attempt.shaped = true;
44683
+ return true;
44684
+ }
44685
+ }
44686
+
43856
44687
  // src/v2/hidden-completion.ts
44688
+ var META_PREFIX = "opencode2_hidden_children:";
44689
+ var POLL_INTERVAL_MS = 200;
44690
+ function hiddenChildrenMetaKey(projectIdentity) {
44691
+ return `${META_PREFIX}${projectIdentity}`;
44692
+ }
44693
+ function emptyMeta() {
44694
+ return { version: 1, active: {}, retired_children: [] };
44695
+ }
44696
+ function isModel(value) {
44697
+ if (!value || typeof value !== "object")
44698
+ return false;
44699
+ const model = value;
44700
+ return typeof model.providerID === "string" && typeof model.modelID === "string";
44701
+ }
44702
+ function isRole(value) {
44703
+ return value === "historian" || value === "dreamer";
44704
+ }
44705
+ function isPersistedChild(value) {
44706
+ if (!value || typeof value !== "object")
44707
+ return false;
44708
+ const child = value;
44709
+ return typeof child.id === "string" && isRole(child.role) && typeof child.generation === "string" && typeof child.title === "string" && isModel(child.model) && typeof child.created_at === "number" && typeof child.title_reasserted === "boolean";
44710
+ }
44711
+ function parseMeta(value) {
44712
+ if (value === null)
44713
+ return emptyMeta();
44714
+ let parsed;
44715
+ try {
44716
+ parsed = JSON.parse(value);
44717
+ } catch (error) {
44718
+ throw new Error("Invalid OpenCode 2 hidden-child metadata JSON", { cause: error });
44719
+ }
44720
+ if (!parsed || typeof parsed !== "object") {
44721
+ throw new Error("Invalid OpenCode 2 hidden-child metadata");
44722
+ }
44723
+ const candidate = parsed;
44724
+ if (candidate.version !== 1 || !candidate.active || !candidate.retired_children) {
44725
+ throw new Error("Unsupported OpenCode 2 hidden-child metadata version");
44726
+ }
44727
+ const active = {};
44728
+ for (const role of ["historian", "dreamer"]) {
44729
+ const child = candidate.active[role];
44730
+ if (child !== undefined) {
44731
+ if (!isPersistedChild(child) || child.role !== role) {
44732
+ throw new Error(`Invalid OpenCode 2 ${role} child metadata`);
44733
+ }
44734
+ active[role] = child;
44735
+ }
44736
+ }
44737
+ const retired = candidate.retired_children;
44738
+ if (!Array.isArray(retired) || retired.some((child) => !isPersistedChild(child) || typeof child.retired_at !== "number" || typeof child.reason !== "string")) {
44739
+ throw new Error("Invalid OpenCode 2 retired-child metadata");
44740
+ }
44741
+ return { version: 1, active, retired_children: retired };
44742
+ }
44743
+
44744
+ class HiddenChildStateStore {
44745
+ db;
44746
+ key;
44747
+ constructor(db, projectIdentity) {
44748
+ this.db = db;
44749
+ this.key = hiddenChildrenMetaKey(projectIdentity);
44750
+ }
44751
+ read() {
44752
+ const row = this.db.prepare("SELECT value FROM schema_migrations_meta WHERE key = ?").get(this.key);
44753
+ return parseMeta(row?.value ?? null);
44754
+ }
44755
+ mutate(change) {
44756
+ return this.db.transaction(() => {
44757
+ const state = this.read();
44758
+ const result = change(state);
44759
+ this.db.prepare(`INSERT INTO schema_migrations_meta (key, value) VALUES (?, ?)
44760
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(this.key, JSON.stringify(state));
44761
+ return result;
44762
+ })();
44763
+ }
44764
+ put(child) {
44765
+ this.mutate((state) => {
44766
+ state.active[child.role] = child;
44767
+ });
44768
+ }
44769
+ updateModel(child, model) {
44770
+ return this.mutate((state) => {
44771
+ const active = state.active[child.role];
44772
+ if (!active || active.id !== child.id)
44773
+ return { ...child, model };
44774
+ active.model = model;
44775
+ return { ...active };
44776
+ });
44777
+ }
44778
+ markTitleReasserted(child) {
44779
+ return this.mutate((state) => {
44780
+ const active = state.active[child.role];
44781
+ if (!active || active.id !== child.id)
44782
+ return { ...child, title_reasserted: true };
44783
+ active.title_reasserted = true;
44784
+ return { ...active };
44785
+ });
44786
+ }
44787
+ retire(child, reason) {
44788
+ this.mutate((state) => {
44789
+ const active = state.active[child.role];
44790
+ if (!active || active.id !== child.id)
44791
+ return;
44792
+ state.retired_children.push({
44793
+ ...active,
44794
+ retired_at: Date.now(),
44795
+ reason
44796
+ });
44797
+ delete state.active[child.role];
44798
+ });
44799
+ }
44800
+ }
44801
+ function modelKey(model) {
44802
+ return `${model.providerID}/${model.modelID}`;
44803
+ }
44804
+ function sameModel(left, right) {
44805
+ return modelKey(left) === modelKey(right) && left.variant === right.variant;
44806
+ }
44807
+ function configuredHead(identity) {
44808
+ const candidates = [identity.model, ...identity.configuredModels ?? []];
44809
+ for (const candidate of candidates) {
44810
+ const entry = toModelEntry(candidate);
44811
+ const parsed = entry ? parseProviderModel(entry.model) : null;
44812
+ if (parsed)
44813
+ return { ...parsed, ...entry?.qualifier ? { variant: entry.qualifier } : {} };
44814
+ }
44815
+ return;
44816
+ }
44817
+ function roleFor(identity) {
44818
+ return identity.kind === "dreamer-task" ? "dreamer" : "historian";
44819
+ }
44820
+ function roleTitle(role) {
44821
+ return role === "historian" ? "Magic Context historian" : "Magic Context dreamer";
44822
+ }
44823
+ function roleAgent(role) {
44824
+ return role === "historian" ? HIDDEN_HISTORIAN_AGENT : HIDDEN_DREAMER_AGENT;
44825
+ }
44826
+ function promptText(request) {
44827
+ const parts = request.body.parts;
44828
+ return Array.isArray(parts) ? parts.flatMap((part) => part && typeof part === "object" && typeof part.text === "string" ? [part.text] : []).join(`
44829
+ `) : "";
44830
+ }
43857
44831
  function meter(system, prompt, text) {
43858
44832
  return {
43859
44833
  input: estimateTokens(system) + estimateTokens(prompt),
@@ -43862,115 +44836,294 @@ function meter(system, prompt, text) {
43862
44836
  cacheWrite: 0
43863
44837
  };
43864
44838
  }
43865
- async function createV2HiddenCompletionExecutor(host, currentModel) {
44839
+ function successfulReusableAssistant(row) {
44840
+ return row !== undefined && typeof row.data.finish === "string" && row.data.error === undefined && row.data.tokens !== undefined;
44841
+ }
44842
+ function assistantText(row) {
44843
+ const text = (row.data.content ?? []).flatMap((part) => part.type === "text" && typeof part.text === "string" ? [part.text] : []).join("");
44844
+ return text.length > 0 ? text : null;
44845
+ }
44846
+ function errorText(value) {
44847
+ if (value instanceof Error)
44848
+ return value.message;
44849
+ if (typeof value === "string")
44850
+ return value;
44851
+ try {
44852
+ return JSON.stringify(value);
44853
+ } catch {
44854
+ return String(value);
44855
+ }
44856
+ }
44857
+ function requestModel(request, current) {
44858
+ const requested = request.body.model;
44859
+ if (requested && typeof requested.providerID === "string" && typeof requested.modelID === "string") {
44860
+ return {
44861
+ providerID: requested.providerID,
44862
+ modelID: requested.modelID,
44863
+ ...typeof request.body.variant === "string" ? { variant: request.body.variant } : {}
44864
+ };
44865
+ }
44866
+ return current;
44867
+ }
44868
+ function withReader(openReader, read) {
44869
+ const reader = openReader();
44870
+ try {
44871
+ return read(reader);
44872
+ } finally {
44873
+ reader.close?.();
44874
+ }
44875
+ }
44876
+ async function sleepUntilPoll(signal, deadline) {
44877
+ if (signal?.aborted)
44878
+ throw new Error("Hidden completion prompt aborted");
44879
+ const delay = Math.min(POLL_INTERVAL_MS, Math.max(0, deadline - Date.now()));
44880
+ if (delay <= 0)
44881
+ return;
44882
+ await new Promise((resolve, reject) => {
44883
+ const timer = setTimeout(resolve, delay);
44884
+ const abort = () => {
44885
+ clearTimeout(timer);
44886
+ reject(new Error("Hidden completion prompt aborted"));
44887
+ };
44888
+ signal?.addEventListener("abort", abort, { once: true });
44889
+ if (signal?.aborted)
44890
+ abort();
44891
+ else
44892
+ setTimeout(() => {
44893
+ signal?.removeEventListener("abort", abort);
44894
+ }, delay);
44895
+ });
44896
+ }
44897
+ async function awaitAssistantRow(openReader, childID, afterSeq, deadline, signal) {
44898
+ for (;; ) {
44899
+ const row = withReader(openReader, (reader) => reader.latestAssistant(childID));
44900
+ if (row && row.seq > afterSeq) {
44901
+ if (row.data.error !== undefined) {
44902
+ throw new Error(`Hidden completion provider error: ${errorText(row.data.error)}`);
44903
+ }
44904
+ if (typeof row.data.finish === "string")
44905
+ return row;
44906
+ }
44907
+ if (Date.now() >= deadline) {
44908
+ throw new Error("Hidden completion timed out waiting for a persisted assistant row");
44909
+ }
44910
+ await sleepUntilPoll(signal, deadline);
44911
+ }
44912
+ }
44913
+ async function createV2HiddenCompletionExecutor(host, options) {
43866
44914
  const runs = new WeakMap;
43867
- const pending = new Map;
43868
- const modelKey = (model) => `${model.providerID}/${model.modelID}`;
43869
- const requireModel = async (sessionID) => {
43870
- const model = sessionID ? await currentModel(sessionID) : null;
43871
- if (!model)
43872
- throw new HiddenCompletionRefusal("hidden_model_unsupported", "Hidden completion requires an existing session with a resolved current model", true);
43873
- return model;
43874
- };
43875
- const refuseModel = (requested, actual, terminal = false) => {
43876
- throw new HiddenCompletionRefusal("hidden_model_unsupported", `Hidden completion model ${requested} is unavailable on opencode2; use the session model ${actual} in historian.model or dreaming task model configuration, or unset the configured model`, terminal);
43877
- };
43878
- await host.hook("generate", async (draft) => {
43879
- const last = draft.messages.at(-1);
43880
- if (last?.role !== "user" || last.content.length !== 1)
43881
- return;
43882
- const part = last.content[0];
43883
- if (part?.type !== "text" || typeof part.text !== "string")
43884
- return;
43885
- const attempt = pending.get(part.text);
43886
- if (!attempt || draft.sessionID !== attempt.run.identity.parentSessionId)
43887
- return;
43888
- const actual = `${draft.model.providerID}/${draft.model.id}`;
43889
- if (modelKey(attempt.model) !== actual)
43890
- refuseModel(modelKey(attempt.model), actual);
43891
- if (attempt.request.signal?.aborted)
43892
- throw new Error("Hidden completion prompt aborted");
43893
- const body = attempt.request.body;
43894
- if (typeof body.variant === "string" && body.variant !== draft.model.variant) {
43895
- refuseModel(`${actual} (${body.variant})`, `${actual} (${draft.model.variant ?? "default variant"})`);
44915
+ const store = new HiddenChildStateStore(options.db, options.projectIdentity);
44916
+ const generation = options.generation ?? "opencode2";
44917
+ const roleTails = new Map;
44918
+ const persisted = store.read();
44919
+ for (const child of [...Object.values(persisted.active), ...persisted.retired_children]) {
44920
+ if (child)
44921
+ options.hook.registerChild(child.id);
44922
+ }
44923
+ const acquireRole = async (role) => {
44924
+ const previous = roleTails.get(role) ?? Promise.resolve();
44925
+ let release;
44926
+ const gate = new Promise((resolve) => {
44927
+ release = resolve;
44928
+ });
44929
+ const tail = previous.then(() => gate);
44930
+ roleTails.set(role, tail);
44931
+ await previous;
44932
+ return () => {
44933
+ release();
44934
+ if (roleTails.get(role) === tail)
44935
+ roleTails.delete(role);
44936
+ };
44937
+ };
44938
+ const resolveHead = async (identity) => {
44939
+ const configured = configuredHead(identity);
44940
+ if (configured)
44941
+ return configured;
44942
+ if (!identity.parentSessionId) {
44943
+ throw new HiddenCompletionRefusal("hidden_model_unsupported", "Hidden completion requires a configured model or an existing parent session model", true);
43896
44944
  }
43897
- const parts = body.parts;
43898
- if (!Array.isArray(parts) || parts.some((item) => item.type !== "text" || typeof item.text !== "string")) {
43899
- throw new HiddenCompletionRefusal("hidden_prompt_unrecognized", "Hidden completion accepts text-only calibrated prompts", true);
44945
+ const parent = await host.get({ sessionID: identity.parentSessionId });
44946
+ if (!parent.model) {
44947
+ throw new HiddenCompletionRefusal("hidden_model_unsupported", "Hidden completion could not resolve the parent session model", true);
43900
44948
  }
43901
- draft.system = [
43902
- {
43903
- type: "text",
43904
- text: typeof body.system === "string" ? body.system : attempt.run.identity.system
44949
+ return {
44950
+ providerID: parent.model.providerID,
44951
+ modelID: parent.model.id,
44952
+ ...parent.model.variant ? { variant: parent.model.variant } : {}
44953
+ };
44954
+ };
44955
+ const switchChildModel = async (run, requested) => {
44956
+ if (sameModel(run.child.model, requested))
44957
+ return;
44958
+ await host.switchModel({
44959
+ sessionID: run.child.id,
44960
+ model: {
44961
+ providerID: requested.providerID,
44962
+ id: requested.modelID,
44963
+ ...requested.variant ? { variant: requested.variant } : {}
43905
44964
  }
43906
- ];
43907
- draft.messages = [
43908
- { role: "user", content: parts.map((item) => ({ type: "text", text: item.text })) }
43909
- ];
43910
- draft.tools = {};
43911
- if (attempt.run.identity.maxOutputTokens !== undefined)
43912
- draft.options.maxOutputTokens = attempt.run.identity.maxOutputTokens;
43913
- attempt.shaped = true;
43914
- });
44965
+ });
44966
+ run.child = store.updateModel(run.child, requested);
44967
+ };
44968
+ const retire = (run, reason) => {
44969
+ if (run.retired)
44970
+ return;
44971
+ store.retire(run.child, reason);
44972
+ run.retired = true;
44973
+ };
44974
+ const interruptAndRetire = async (run, reason) => {
44975
+ try {
44976
+ await host.interrupt({ sessionID: run.child.id });
44977
+ } finally {
44978
+ retire(run, reason);
44979
+ }
44980
+ };
43915
44981
  return {
43916
44982
  capabilities: { tools: false, harness: "opencode2" },
43917
44983
  async open(identity) {
43918
- const model = await requireModel(identity.parentSessionId);
43919
- const configured = (identity.configuredModels ?? (identity.model ? [identity.model] : [])).map(toModelEntry).filter((entry) => entry !== undefined);
43920
- if (configured.length && !configured.some((entry) => entry.model === modelKey(model))) {
43921
- refuseModel(configured.map((entry) => entry.model).join(", "), modelKey(model), true);
43922
- }
43923
- const handle = { id: `mc:hidden:${crypto.randomUUID()}` };
43924
- runs.set(handle, { identity });
43925
- return handle;
44984
+ const role = roleFor(identity);
44985
+ const releaseRole = await acquireRole(role);
44986
+ let openedChild;
44987
+ try {
44988
+ await options.ensureAgent?.();
44989
+ const head = await resolveHead(identity);
44990
+ let active = store.read().active[role];
44991
+ if (active && active.generation !== generation) {
44992
+ store.retire(active, "host-generation-changed");
44993
+ active = undefined;
44994
+ }
44995
+ if (active) {
44996
+ const activeID = active.id;
44997
+ const latest = withReader(options.openReader, (reader) => reader.latestAssistant(activeID));
44998
+ if (!successfulReusableAssistant(latest)) {
44999
+ store.retire(active, "newest-assistant-not-reusable");
45000
+ active = undefined;
45001
+ }
45002
+ }
45003
+ if (!active) {
45004
+ const title = roleTitle(role);
45005
+ const created = await host.create({
45006
+ title,
45007
+ agent: roleAgent(role),
45008
+ model: {
45009
+ providerID: head.providerID,
45010
+ id: head.modelID,
45011
+ ...head.variant ? { variant: head.variant } : {}
45012
+ },
45013
+ location: { directory: identity.directory },
45014
+ metadata: { magic_context: "hidden-run", role }
45015
+ });
45016
+ if (!created.id)
45017
+ throw new Error("OpenCode 2 did not return a child session id");
45018
+ active = {
45019
+ id: created.id,
45020
+ role,
45021
+ generation,
45022
+ title,
45023
+ model: head,
45024
+ created_at: Date.now(),
45025
+ title_reasserted: false
45026
+ };
45027
+ store.put(active);
45028
+ options.hook.registerChild(active.id);
45029
+ }
45030
+ openedChild = active;
45031
+ const handle = { id: active.id, childSessionId: active.id };
45032
+ const run = {
45033
+ identity,
45034
+ role,
45035
+ child: active,
45036
+ releaseRole,
45037
+ failed: false,
45038
+ retired: false
45039
+ };
45040
+ runs.set(handle, run);
45041
+ await switchChildModel(run, head);
45042
+ return handle;
45043
+ } catch (error) {
45044
+ if (openedChild)
45045
+ store.retire(openedChild, "hidden-run-open-failed");
45046
+ releaseRole();
45047
+ throw error;
45048
+ }
43926
45049
  },
43927
45050
  async attempt(handle, request) {
43928
45051
  const run = runs.get(handle);
43929
45052
  if (!run)
43930
45053
  throw new Error("Unknown hidden completion run");
43931
- const sessionID = run.identity.parentSessionId;
43932
- const actual = await requireModel(sessionID);
43933
- if (sessionID === undefined)
43934
- throw new Error("Hidden completion session id missing");
43935
- const requested = request.body.model ?? actual;
43936
- if (modelKey(requested) !== modelKey(actual))
43937
- refuseModel(modelKey(requested), modelKey(actual));
43938
- if (request.signal?.aborted)
45054
+ if (request.signal?.aborted) {
45055
+ await interruptAndRetire(run, "aborted-before-prompt");
43939
45056
  throw new Error("Hidden completion prompt aborted");
43940
- const marker = `${handle.id}:${crypto.randomUUID()}`;
43941
- const state = { run, request, model: actual, shaped: false };
43942
- pending.set(marker, state);
45057
+ }
45058
+ const requested = requestModel(request, run.child.model);
45059
+ await switchChildModel(run, requested);
45060
+ const baseline = withReader(options.openReader, (reader) => reader.latestSequence(run.child.id));
45061
+ const marker = `mc:hidden:${crypto.randomUUID()}:${crypto.randomUUID()}`;
45062
+ const attempt = {
45063
+ childSessionId: run.child.id,
45064
+ identity: run.identity,
45065
+ request,
45066
+ shaped: false
45067
+ };
45068
+ options.hook.registerAttempt(marker, attempt);
45069
+ const deadline = Date.now() + run.identity.timeoutMs;
45070
+ let abortReject;
45071
+ const aborted = new Promise((_resolve, reject) => {
45072
+ abortReject = reject;
45073
+ });
45074
+ const onAbort = () => {
45075
+ interruptAndRetire(run, "prompt-aborted").finally(() => abortReject(new Error("Hidden completion prompt aborted")));
45076
+ };
45077
+ const deadlineTimer = setTimeout(() => {
45078
+ interruptAndRetire(run, "prompt-timeout").finally(() => abortReject(new Error("Hidden completion prompt timed out")));
45079
+ }, Math.max(0, deadline - Date.now()));
45080
+ request.signal?.addEventListener("abort", onAbort, { once: true });
43943
45081
  try {
43944
- const generation = host.generate({ sessionID, prompt: marker }, { signal: request.signal });
43945
- const result = await new Promise((resolve, reject) => {
43946
- const abort = () => reject(new Error("Hidden completion prompt aborted"));
43947
- request.signal?.addEventListener("abort", abort, { once: true });
43948
- generation.then(resolve, reject).finally(() => {
43949
- request.signal?.removeEventListener("abort", abort);
43950
- pending.delete(marker);
43951
- });
43952
- if (request.signal?.aborted)
43953
- abort();
43954
- });
43955
- if (!state.shaped)
43956
- throw new HiddenCompletionRefusal("hidden_prompt_unrecognized", "Host did not dispatch the hidden generate hook", true);
43957
- const prompt = request.body.parts.map((part) => part.text).join(`
43958
- `);
45082
+ await Promise.race([
45083
+ host.prompt({ sessionID: run.child.id, text: marker }),
45084
+ aborted
45085
+ ]);
45086
+ await Promise.race([host.wait({ sessionID: run.child.id }), aborted]);
45087
+ clearTimeout(deadlineTimer);
45088
+ const row = await Promise.race([
45089
+ awaitAssistantRow(options.openReader, run.child.id, baseline, deadline, request.signal),
45090
+ aborted
45091
+ ]);
45092
+ if (!attempt.shaped) {
45093
+ throw new HiddenCompletionRefusal("hidden_prompt_unrecognized", "Host did not dispatch the hidden child context hook", true);
45094
+ }
45095
+ if (!run.child.title_reasserted) {
45096
+ await host.update({ sessionID: run.child.id, title: run.child.title });
45097
+ run.child = store.markTitleReasserted(run.child);
45098
+ }
45099
+ const text = assistantText(row);
43959
45100
  const system = typeof request.body.system === "string" ? request.body.system : run.identity.system;
43960
- const usage = meter(system, prompt, result.text);
43961
- usage.input += run.usage?.input ?? 0;
43962
- usage.output += run.usage?.output ?? 0;
43963
- run.usage = usage;
45101
+ const tokens = row.data.tokens;
43964
45102
  run.completion = {
43965
- text: result.text,
43966
- usage,
43967
- lengthCapped: false,
43968
- providerId: actual.providerID,
43969
- modelId: actual.modelID
45103
+ text,
45104
+ reasoning: null,
45105
+ usage: tokens ? {
45106
+ input: tokens.input,
45107
+ output: tokens.output,
45108
+ cacheRead: tokens.cache.read,
45109
+ cacheWrite: tokens.cache.write
45110
+ } : meter(system, promptText(request), text ?? ""),
45111
+ lengthCapped: ["length", "max_tokens"].includes(row.data.finish ?? ""),
45112
+ providerId: row.data.model?.providerID ?? requested.providerID,
45113
+ modelId: row.data.model?.id ?? requested.modelID
43970
45114
  };
45115
+ } catch (error) {
45116
+ run.failed = true;
45117
+ if (request.signal?.aborted && !run.retired) {
45118
+ await interruptAndRetire(run, "prompt-aborted");
45119
+ } else if (error instanceof Error && error.message.includes("timed out") && !run.retired) {
45120
+ await interruptAndRetire(run, "prompt-timeout");
45121
+ }
45122
+ throw error;
43971
45123
  } finally {
43972
- if (!request.signal?.aborted)
43973
- pending.delete(marker);
45124
+ clearTimeout(deadlineTimer);
45125
+ request.signal?.removeEventListener("abort", onAbort);
45126
+ options.hook.releaseAttempt(marker);
43974
45127
  }
43975
45128
  },
43976
45129
  async collect(handle) {
@@ -43979,18 +45132,27 @@ async function createV2HiddenCompletionExecutor(host, currentModel) {
43979
45132
  throw new Error("Hidden completion has no settled output");
43980
45133
  return completion;
43981
45134
  },
43982
- async close() {}
45135
+ async close(handle, settlement) {
45136
+ if (!handle)
45137
+ return;
45138
+ const run = runs.get(handle);
45139
+ if (!run)
45140
+ return;
45141
+ try {
45142
+ if (!run.completion && (run.failed || !settlement.promptSettled)) {
45143
+ retire(run, "hidden-run-failed");
45144
+ }
45145
+ } finally {
45146
+ runs.delete(handle);
45147
+ run.releaseRole();
45148
+ }
45149
+ }
43983
45150
  };
43984
45151
  }
43985
45152
 
43986
45153
  // src/v2/store-reader.ts
43987
- import { join as join11 } from "node:path";
43988
- function sourceDatabaseFilename(channel, env = process.env) {
43989
- return env.OPENCODE_DB ?? (["latest", "dev", "beta", "next", "prod"].includes(channel) || env.OPENCODE_DISABLE_CHANNEL_DB === "1" || env.OPENCODE_DISABLE_CHANNEL_DB === "true" ? "opencode.db" : `opencode-${channel.replace(/[^a-zA-Z0-9._-]/g, "")}.db`);
43990
- }
43991
45154
  function gaDatabasePath(dataHome, channel = "latest", env = process.env) {
43992
- const filename = sourceDatabaseFilename(channel, env);
43993
- return filename === ":memory:" ? filename : join11(dataHome, "opencode", filename);
45155
+ return resolveOpenCodeDbPath("v2", { dataHome, channel, env }).path;
43994
45156
  }
43995
45157
  function decode(row) {
43996
45158
  const data = JSON.parse(row.data);
@@ -44007,6 +45169,12 @@ class V2StoreReader {
44007
45169
  db;
44008
45170
  constructor(path) {
44009
45171
  this.db = new Database(path, { readonly: true, fileMustExist: true });
45172
+ try {
45173
+ assertOpenCodeStoreGeneration(this.db, "v2", path);
45174
+ } catch (error) {
45175
+ this.db.close();
45176
+ throw error;
45177
+ }
44010
45178
  }
44011
45179
  close() {
44012
45180
  this.db.close();
@@ -44032,6 +45200,16 @@ class V2StoreReader {
44032
45200
  idleRows(sessionID, after = -1) {
44033
45201
  return this.all(sessionID, after, "idle");
44034
45202
  }
45203
+ latestSequence(sessionID) {
45204
+ const row = this.db.prepare("SELECT MAX(seq) AS seq FROM session_message WHERE session_id = ?").get(sessionID);
45205
+ return typeof row?.seq === "number" ? row.seq : -1;
45206
+ }
45207
+ latestAssistant(sessionID) {
45208
+ const row = this.db.prepare(`SELECT id, session_id, type, seq, data FROM session_message
45209
+ WHERE session_id = ? AND type = 'assistant'
45210
+ ORDER BY seq DESC LIMIT 1`).get(sessionID);
45211
+ return row ? decode(row) : undefined;
45212
+ }
44035
45213
  window(sessionID) {
44036
45214
  return this.db.transaction(() => {
44037
45215
  const cut = this.latestCompaction(sessionID);
@@ -44281,7 +45459,7 @@ function rawMessages(rows, history = rows) {
44281
45459
  const ordinals = new Map(history.filter(isRawRow).map((row, index) => [row.id, index + 1]));
44282
45460
  return rows.filter(isRawRow).map((row) => ({
44283
45461
  id: row.id,
44284
- ordinal: ordinals.get(row.id),
45462
+ ordinal: ordinals.get(row.id) ?? 0,
44285
45463
  role: row.type === "assistant" ? "assistant" : "user",
44286
45464
  createdAt: row.data.time?.created,
44287
45465
  parts: row.type === "assistant" ? (row.data.content ?? []).map((part) => {
@@ -44315,20 +45493,96 @@ function createHostSeams(context, read, liveModels) {
44315
45493
  hostRefuse: (_client, sessionID) => interruptBeforeProvider(context.session, sessionID)
44316
45494
  };
44317
45495
  }
45496
+ function toolResultText(result) {
45497
+ const content = result?.content ?? result?.output;
45498
+ if (typeof content === "string")
45499
+ return content;
45500
+ if (content && typeof content === "object" && !Array.isArray(content)) {
45501
+ const record = content;
45502
+ if (typeof record.text === "string")
45503
+ return record.text;
45504
+ if (typeof record.value === "string")
45505
+ return record.value;
45506
+ return "";
45507
+ }
45508
+ if (!Array.isArray(content))
45509
+ return "";
45510
+ return content.map((part) => {
45511
+ if (typeof part === "string")
45512
+ return part;
45513
+ if (!part || typeof part !== "object")
45514
+ return "";
45515
+ const record = part;
45516
+ if (typeof record.text === "string")
45517
+ return record.text;
45518
+ if (typeof record.value === "string")
45519
+ return record.value;
45520
+ return "";
45521
+ }).filter(Boolean).join(`
45522
+ `);
45523
+ }
45524
+ function catalogModels(listed) {
45525
+ const rows = Array.isArray(listed) ? listed : listed && typeof listed === "object" && Array.isArray(listed.data) ? listed.data : [];
45526
+ return rows.flatMap((row) => {
45527
+ if (!row || typeof row !== "object")
45528
+ return [];
45529
+ const model = row;
45530
+ if (typeof model.id !== "string" || typeof model.providerID !== "string")
45531
+ return [];
45532
+ const contextLimit = model.limit?.context;
45533
+ if (typeof contextLimit !== "number" || !Number.isFinite(contextLimit))
45534
+ return [];
45535
+ return [{ id: model.id, providerID: model.providerID, limit: { context: contextLimit } }];
45536
+ });
45537
+ }
45538
+ function applyV2PromptSurfaceTools(draft, runtime, config) {
45539
+ if (!draft.tools)
45540
+ return;
45541
+ const modelKey = `${draft.model.providerID}/${draft.model.id}`;
45542
+ const registration = runtime.resolveRegistration(config, modelKey);
45543
+ for (const id of ACTIVE_TOOL_IDS) {
45544
+ const tool = draft.tools[id];
45545
+ if (!tool)
45546
+ continue;
45547
+ tool.description = registration.descriptionFor(id, tool.description);
45548
+ }
45549
+ }
44318
45550
  async function registerContext(context) {
44319
45551
  const directory = context.location.directory;
44320
45552
  const config = loadPluginConfigDetailed(directory).config;
44321
45553
  if (!config.enabled || !isCompactionEnabled(config))
44322
45554
  return;
45555
+ const conflicts = detectConflicts(directory, {
45556
+ compactionEnabled: true,
45557
+ hostGeneration: "v2"
45558
+ });
45559
+ if (conflicts.hasConflict) {
45560
+ console.warn(`[magic-context] v2 setup disabled by conflicting context hooks: ${conflicts.reasons.join("; ")}`);
45561
+ return;
45562
+ }
44323
45563
  const folds = new FoldOwner(context.storage);
44324
45564
  const limits = new Map;
44325
45565
  const queriedModels = new Set;
44326
45566
  const liveModels = new Map;
44327
- const hostGenerate = context.session.generate?.bind(context.session);
44328
- const hiddenCompletionExecutor = hostGenerate ? await createV2HiddenCompletionExecutor({
44329
- hook: (name, callback) => context.session.hook(name, callback),
44330
- generate: (input, options) => hostGenerate(input, options)
44331
- }, (sessionID) => liveModels.get(sessionID) ?? null) : undefined;
45567
+ const promptSurfaceRuntime = createPromptSurfaceRuntime({
45568
+ harness: "opencode2",
45569
+ directory,
45570
+ warn: (message) => console.warn(`[magic-context] config warning: ${message}`)
45571
+ });
45572
+ let db;
45573
+ try {
45574
+ db = openDatabase() ?? undefined;
45575
+ } catch {}
45576
+ const hiddenChildHook = new HiddenChildHook;
45577
+ await registerHiddenChildAgents(context.agent);
45578
+ let hiddenAgentsReady;
45579
+ const hiddenCompletionExecutor = db && isDatabasePersisted(db) ? await createV2HiddenCompletionExecutor(context.session, {
45580
+ db,
45581
+ projectIdentity: resolveProjectIdentity(directory) ?? directory,
45582
+ hook: hiddenChildHook,
45583
+ ensureAgent: () => hiddenAgentsReady ??= context.agent.reload(),
45584
+ openReader: () => new V2StoreReader(gaDatabasePath(getDataDir(), process.env.OPENCODE_CHANNEL ?? "latest"))
45585
+ }) : undefined;
44332
45586
  const dreamTrigger = hiddenCompletionExecutor && config.dreamer && !config.dreamer.disable ? startDreamTrigger(context, {
44333
45587
  config: config.dreamer,
44334
45588
  executor: hiddenCompletionExecutor,
@@ -44349,16 +45603,17 @@ async function registerContext(context) {
44349
45603
  let toolDuties;
44350
45604
  await context.tool.hook("execute.before", (draft) => assertExecutableToolInput(draft.input));
44351
45605
  await context.tool.hook("execute.after", async (draft) => {
44352
- if (!db || draft.status !== "completed")
45606
+ if (!db)
45607
+ return;
45608
+ if (draft.status && draft.status !== "completed")
44353
45609
  return;
44354
45610
  try {
44355
45611
  toolDuties ??= createToolExecuteAfterHook({ db, channel1StateBySession: channel1 });
44356
- const content = draft.result?.content;
44357
- const text = typeof content === "string" ? content : Array.isArray(content) ? content.filter((part) => part.type === "text").map((part) => part.text).join(`
44358
- `) : "";
45612
+ const text = toolResultText(draft.result);
44359
45613
  const output = { output: text };
44360
45614
  await toolDuties({ ...draft, args: draft.input }, output);
44361
45615
  if (draft.result && output.output !== text) {
45616
+ const content = draft.result.content;
44362
45617
  if (typeof content === "string")
44363
45618
  draft.result.content = output.output;
44364
45619
  else if (Array.isArray(content) && output.output.startsWith(text))
@@ -44383,7 +45638,6 @@ async function registerContext(context) {
44383
45638
  getCount: (sessionID) => read(sessionID).length
44384
45639
  });
44385
45640
  let transform;
44386
- let db;
44387
45641
  const refuseIfUnsafe = async (draft) => {
44388
45642
  let unsafe = false;
44389
45643
  try {
@@ -44397,10 +45651,8 @@ async function registerContext(context) {
44397
45651
  const tokens = latest?.data.tokens;
44398
45652
  const modelKey = `${draft.model.providerID}/${draft.model.id}`;
44399
45653
  if (!queriedModels.has(modelKey)) {
44400
- const catalog = await context.catalog.model.list({
44401
- location: context.location
44402
- });
44403
- for (const model of catalog.data)
45654
+ const catalog = await Promise.resolve(context.model.list());
45655
+ for (const model of catalogModels(catalog))
44404
45656
  limits.set(`${model.providerID}/${model.id}`, model.limit.context);
44405
45657
  queriedModels.add(modelKey);
44406
45658
  }
@@ -44420,7 +45672,8 @@ async function registerContext(context) {
44420
45672
  } finally {
44421
45673
  reader.close();
44422
45674
  }
44423
- } catch {
45675
+ } catch (error) {
45676
+ console.warn("[magic-context] v2 refuseIfUnsafe", error);
44424
45677
  unsafe = true;
44425
45678
  }
44426
45679
  if (unsafe)
@@ -44473,6 +45726,26 @@ async function registerContext(context) {
44473
45726
  }
44474
45727
  });
44475
45728
  await context.session.hook("context", async (draft) => {
45729
+ if (hiddenChildHook.apply(draft))
45730
+ return;
45731
+ liveModels.set(draft.sessionID, {
45732
+ providerID: draft.model.providerID,
45733
+ modelID: draft.model.id
45734
+ });
45735
+ variants.set(draft.sessionID, draft.model.variant);
45736
+ agents.set(draft.sessionID, draft.agent);
45737
+ applyV2PromptSurfaceTools(draft, promptSurfaceRuntime, config.prompt_surface);
45738
+ if (context.tool.transform) {
45739
+ const modelKey = `${draft.model.providerID}/${draft.model.id}`;
45740
+ const registration = promptSurfaceRuntime.resolveRegistration(config.prompt_surface, modelKey);
45741
+ await context.tool.transform((editor) => {
45742
+ for (const id of ACTIVE_TOOL_IDS) {
45743
+ editor.update(id, (tool) => {
45744
+ tool.description = registration.descriptionFor(id, tool.description);
45745
+ });
45746
+ }
45747
+ });
45748
+ }
44476
45749
  let postFold = false;
44477
45750
  try {
44478
45751
  if (await refuseIfUnsafe(draft))
@@ -44589,6 +45862,9 @@ async function registerContext(context) {
44589
45862
  const mapped = adaptPayload(draft, admitted);
44590
45863
  await transform({}, mapped);
44591
45864
  mapped.commit();
45865
+ if (db) {
45866
+ await deliverPendingChannel2(context, db, draft.sessionID, channel1.get(draft.sessionID));
45867
+ }
44592
45868
  if (checkpoint && submitted !== undefined) {
44593
45869
  const head = draft.messages.find((message) => message.id === HEAD_IDS[0]);
44594
45870
  const baseline = head?.content.find((part) => part.type === "text")?.text;
@@ -44628,18 +45904,18 @@ ${baseline}
44628
45904
  }
44629
45905
 
44630
45906
  // src/v2/hooks/update-check.ts
44631
- import { existsSync as existsSync13, readFileSync as readFileSync7 } from "node:fs";
44632
- import { dirname as dirname8, join as join12 } from "node:path";
45907
+ import { existsSync as existsSync14, readFileSync as readFileSync8 } from "node:fs";
45908
+ import { dirname as dirname8, join as join11 } from "node:path";
44633
45909
  import { fileURLToPath as fileURLToPath3 } from "node:url";
44634
45910
  function packageInfo() {
44635
45911
  let directory = dirname8(fileURLToPath3(import.meta.url));
44636
45912
  for (;; ) {
44637
45913
  try {
44638
- const pkg = JSON.parse(readFileSync7(join12(directory, "package.json"), "utf8"));
45914
+ const pkg = JSON.parse(readFileSync8(join11(directory, "package.json"), "utf8"));
44639
45915
  if (pkg.name === "@cortexkit/opencode-magic-context" && typeof pkg.version === "string") {
44640
45916
  return {
44641
45917
  version: pkg.version,
44642
- development: existsSync13(join12(directory, "src/v2/server.ts"))
45918
+ development: existsSync14(join11(directory, "src/v2/server.ts"))
44643
45919
  };
44644
45920
  }
44645
45921
  } catch {}
@@ -44686,7 +45962,17 @@ function startUpdateChecks(context, check = (signal) => {
44686
45962
  }
44687
45963
 
44688
45964
  // src/v2/server.ts
45965
+ function isOpenCode2HostContext(context) {
45966
+ if (typeof context !== "object" || context === null)
45967
+ return false;
45968
+ const session = context.session;
45969
+ return typeof session === "object" && session !== null && typeof session.hook === "function";
45970
+ }
44689
45971
  async function setup(context) {
45972
+ if (!isOpenCode2HostContext(context)) {
45973
+ log("v2 setup() called on a host without session.hook; v1 server lane owns this host, v2 lane inert");
45974
+ return async () => {};
45975
+ }
44690
45976
  setHarness("opencode2");
44691
45977
  const duties = await registerContext(context);
44692
45978
  const checks = loadPluginConfigDetailed(context.location.directory).config.auto_update === false ? undefined : startUpdateChecks(context);
@@ -44710,6 +45996,7 @@ var server = async (ctx) => {
44710
45996
  let hooksDeadlineReason = null;
44711
45997
  let deadlinePhase = null;
44712
45998
  emitBootEnteringBreadcrumb(process.pid, ctx.directory, log, flushLogger);
45999
+ setHarness("opencode");
44713
46000
  const configStartedAt = performance.now();
44714
46001
  beginBootQuietPeriod();
44715
46002
  const configMigrationWarnings = migrateMagicContextConfigLocations(ctx.directory, {
@@ -44749,7 +46036,7 @@ var server = async (ctx) => {
44749
46036
  if (hasBannerEntries)
44750
46037
  setTimeout(async () => {
44751
46038
  try {
44752
- const { sendStatusNotification } = await import("./send-session-notification-xnypkxpx.js");
46039
+ const { sendStatusNotification } = await import("./send-session-notification-jbxknvar.js");
44753
46040
  const clientWithSessions = ctx.client;
44754
46041
  const sessions = await Promise.resolve(clientWithSessions.session?.list?.()).catch(() => null);
44755
46042
  const sessionList = Array.isArray(sessions) ? sessions : sessions?.data;
@@ -44766,7 +46053,7 @@ var server = async (ctx) => {
44766
46053
  log(`[magic-context] opencode_db_missing path=${openCodeDbResolution.path} source=${openCodeDbResolution.source}`);
44767
46054
  setTimeout(async () => {
44768
46055
  try {
44769
- const { sendStatusNotification } = await import("./send-session-notification-xnypkxpx.js");
46056
+ const { sendStatusNotification } = await import("./send-session-notification-jbxknvar.js");
44770
46057
  const clientWithSessions = ctx.client;
44771
46058
  const sessions = await Promise.resolve(clientWithSessions.session?.list?.()).catch(() => null);
44772
46059
  const sessionList = Array.isArray(sessions) ? sessions : sessions?.data;
@@ -45012,12 +46299,12 @@ var server = async (ctx) => {
45012
46299
  {
45013
46300
  const fence = getSchemaFenceRejection();
45014
46301
  if (fence) {
45015
- import("./conflict-warning-hook-jyj54gbe.js").then(({ sendSchemaFenceWarning }) => sendSchemaFenceWarning(ctx.client, ctx.directory, fence));
46302
+ import("./conflict-warning-hook-aktepwpz.js").then(({ sendSchemaFenceWarning }) => sendSchemaFenceWarning(ctx.client, ctx.directory, fence));
45016
46303
  }
45017
46304
  }
45018
46305
  const serverUrl = ctx.serverUrl;
45019
46306
  const serverUrlStr = serverUrl instanceof URL ? serverUrl.toString().replace(/\/$/, "") : typeof serverUrl === "string" ? serverUrl.replace(/\/$/, "") : undefined;
45020
- import("./send-session-notification-xnypkxpx.js").then(({ setNotificationServerUrl }) => {
46307
+ import("./send-session-notification-jbxknvar.js").then(({ setNotificationServerUrl }) => {
45021
46308
  setNotificationServerUrl(serverUrlStr);
45022
46309
  });
45023
46310
  if (conflictResult?.hasConflict) {
@@ -45040,7 +46327,7 @@ var server = async (ctx) => {
45040
46327
  }) => {
45041
46328
  if (!shouldShowAnnouncement())
45042
46329
  return;
45043
- return import("./conflict-warning-hook-jyj54gbe.js").then(({ sendStartupAnnouncement }) => sendStartupAnnouncement(ctx.client, ctx.directory, ANNOUNCEMENT_VERSION, ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER, markAnnouncementSeen));
46330
+ return import("./conflict-warning-hook-aktepwpz.js").then(({ sendStartupAnnouncement }) => sendStartupAnnouncement(ctx.client, ctx.directory, ANNOUNCEMENT_VERSION, ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER, markAnnouncementSeen));
45044
46331
  }).catch(() => {});
45045
46332
  }, 8000);
45046
46333
  }