@cortexkit/opencode-magic-context 0.42.5 → 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 (128) 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/open-opencode-db.d.ts.map +1 -1
  11. package/dist/features/magic-context/dreamer/task-scheduler.d.ts.map +1 -1
  12. package/dist/features/magic-context/memory/embedding-local.d.ts +1 -0
  13. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  14. package/dist/features/magic-context/memory/embedding.d.ts +1 -1
  15. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  16. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  17. package/dist/features/magic-context/migrations.d.ts +12 -0
  18. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  19. package/dist/features/magic-context/project-embedding-registry.d.ts +1 -0
  20. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  21. package/dist/features/magic-context/session-project-backfill.d.ts.map +1 -1
  22. package/dist/features/magic-context/storage-db.d.ts +1 -1
  23. package/dist/features/magic-context/tool-owner-backfill.d.ts.map +1 -1
  24. package/dist/hooks/auto-update-checker/index.d.ts.map +1 -1
  25. package/dist/hooks/magic-context/cache-busting-signals.d.ts +0 -1
  26. package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
  27. package/dist/hooks/magic-context/channel2-delivery.d.ts +8 -0
  28. package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
  29. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts +12 -0
  30. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  31. package/dist/hooks/magic-context/hook.d.ts +4 -4
  32. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  33. package/dist/hooks/magic-context/module-state-sync.d.ts +3 -0
  34. package/dist/hooks/magic-context/module-state-sync.d.ts.map +1 -1
  35. package/dist/hooks/magic-context/module-transport.d.ts +24 -2
  36. package/dist/hooks/magic-context/module-transport.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/module-wire.d.ts +1 -1
  38. package/dist/hooks/magic-context/module-wire.d.ts.map +1 -1
  39. package/dist/hooks/magic-context/producer-window-guard.d.ts +31 -1
  40. package/dist/hooks/magic-context/producer-window-guard.d.ts.map +1 -1
  41. package/dist/hooks/magic-context/read-session-chunk.d.ts +6 -0
  42. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  43. package/dist/hooks/magic-context/read-session-db.d.ts +8 -0
  44. package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/read-session-formatting.d.ts +6 -0
  46. package/dist/hooks/magic-context/read-session-formatting.d.ts.map +1 -1
  47. package/dist/hooks/magic-context/rust-mode-transform.d.ts +30 -0
  48. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  49. package/dist/hooks/magic-context/rust-refusal-recovery.d.ts +28 -0
  50. package/dist/hooks/magic-context/rust-refusal-recovery.d.ts.map +1 -0
  51. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  52. package/dist/hooks/magic-context/transform.d.ts +7 -0
  53. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  54. package/dist/{index-jkrqhc7y.js → index-a9ssj59b.js} +94 -15
  55. package/dist/{index-3wbd8sdq.js → index-djp6rwz6.js} +455 -46
  56. package/dist/{index-p474rw9p.js → index-ngjmj6xx.js} +1 -1
  57. package/dist/{index-d8mgqezq.js → index-s61hh7gv.js} +50 -17
  58. package/dist/{index-1ar7b3e9.js → index-znraa0kv.js} +1 -1
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +1485 -360
  61. package/dist/plugin/embedding-bootstrap.d.ts.map +1 -1
  62. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  63. package/dist/plugin/memory-id-translation.d.ts +23 -0
  64. package/dist/plugin/memory-id-translation.d.ts.map +1 -0
  65. package/dist/plugin/rust-tool-backends.d.ts +1 -1
  66. package/dist/plugin/rust-tool-backends.d.ts.map +1 -1
  67. package/dist/{recomp-orchestrator-k20sfpm2.js → recomp-orchestrator-0xx1364v.js} +4 -4
  68. package/dist/{send-session-notification-xnypkxpx.js → send-session-notification-jbxknvar.js} +2 -2
  69. package/dist/shared/auto-update-provenance.d.ts +9 -0
  70. package/dist/shared/auto-update-provenance.d.ts.map +1 -0
  71. package/dist/shared/conflict-detector.d.ts +5 -1
  72. package/dist/shared/conflict-detector.d.ts.map +1 -1
  73. package/dist/shared/opencode-db-path.d.ts +31 -2
  74. package/dist/shared/opencode-db-path.d.ts.map +1 -1
  75. package/dist/shared/prompt-surface-runtime.d.ts +3 -3
  76. package/dist/shared/prompt-surface-runtime.d.ts.map +1 -1
  77. package/dist/{storage-meta-session-x3j0q9qy.js → storage-meta-session-z818pn1e.js} +2 -2
  78. package/dist/tools/ctx-expand/mode.d.ts +31 -0
  79. package/dist/tools/ctx-expand/mode.d.ts.map +1 -0
  80. package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
  81. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  82. package/dist/tools/ctx-note/constants.d.ts +1 -1
  83. package/dist/tools/ctx-note/constants.d.ts.map +1 -1
  84. package/dist/tools/ctx-note/tools.d.ts.map +1 -1
  85. package/dist/tools/ctx-note/types.d.ts +1 -1
  86. package/dist/tools/ctx-note/types.d.ts.map +1 -1
  87. package/dist/tools/ctx-reduce/constants.d.ts +1 -1
  88. package/dist/tools/ctx-reduce/constants.d.ts.map +1 -1
  89. package/dist/tools/ctx-search/types.d.ts +1 -1
  90. package/dist/tools/ctx-search/types.d.ts.map +1 -1
  91. package/dist/tools/light-descriptions.d.ts +2 -2
  92. package/dist/tools/light-descriptions.d.ts.map +1 -1
  93. package/dist/v2/hidden-completion.d.ts +69 -13
  94. package/dist/v2/hidden-completion.d.ts.map +1 -1
  95. package/dist/v2/hooks/context.d.ts +13 -1
  96. package/dist/v2/hooks/context.d.ts.map +1 -1
  97. package/dist/v2/hooks/hidden-child.d.ts +31 -0
  98. package/dist/v2/hooks/hidden-child.d.ts.map +1 -0
  99. package/dist/v2/hooks/store.d.ts.map +1 -1
  100. package/dist/v2/hooks/types.d.ts +99 -19
  101. package/dist/v2/hooks/types.d.ts.map +1 -1
  102. package/dist/v2/server.d.ts +19 -0
  103. package/dist/v2/server.d.ts.map +1 -1
  104. package/dist/v2/server.js +1781 -253
  105. package/dist/v2/store-reader.d.ts +11 -5
  106. package/dist/v2/store-reader.d.ts.map +1 -1
  107. package/dist/v2/tui/index.d.ts +10 -0
  108. package/dist/v2/tui/index.d.ts.map +1 -0
  109. package/dist/v2/tui/types.d.ts +80 -0
  110. package/dist/v2/tui/types.d.ts.map +1 -0
  111. package/package.json +5 -4
  112. package/src/agents/magic-context-prompt.ts +1 -1
  113. package/src/config/index.ts +31 -1
  114. package/src/config/protected-tokens.test.ts +40 -0
  115. package/src/config/schema/magic-context.ts +8 -3
  116. package/src/shared/auto-update-provenance.ts +42 -0
  117. package/src/shared/conflict-detector.test.ts +53 -0
  118. package/src/shared/conflict-detector.ts +88 -19
  119. package/src/shared/models-dev-cache.test.ts +25 -0
  120. package/src/shared/opencode-db-path.test.ts +96 -0
  121. package/src/shared/opencode-db-path.ts +146 -15
  122. package/src/shared/prompt-surface-a1-golden.md +12 -16
  123. package/src/shared/prompt-surface-runtime.test.ts +20 -0
  124. package/src/shared/prompt-surface-runtime.ts +7 -5
  125. package/src/tui/entry.mjs +11 -1
  126. package/src/v2/tui/host-contract.test.ts +202 -0
  127. package/src/v2/tui/index.ts +270 -0
  128. 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-3wbd8sdq.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,
@@ -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;
@@ -11192,7 +11216,19 @@ async function runSessionProjectBackfill(db, source, options = {}) {
11192
11216
  let identityResolutionsSinceYield = 0;
11193
11217
  let afterSessionId = null;
11194
11218
  for (;; ) {
11195
- 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
+ }
11196
11232
  if (sourcePage.length === 0)
11197
11233
  break;
11198
11234
  afterSessionId = sourcePage.at(-1)?.sessionId ?? afterSessionId;
@@ -11523,17 +11559,47 @@ async function runDeferredV22Backfill(db, options = {}) {
11523
11559
  }
11524
11560
 
11525
11561
  // src/hooks/auto-update-checker/index.ts
11526
- 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";
11527
11563
  import { dirname as dirname4, join as join4 } from "node:path";
11528
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
+
11529
11595
  // src/hooks/auto-update-checker/checker.ts
11530
11596
  var import_comment_json = __toESM(require_src2(), 1);
11531
11597
  import { spawn } from "node:child_process";
11532
11598
  import {
11533
11599
  accessSync,
11534
- existsSync as existsSync5,
11600
+ existsSync as existsSync6,
11535
11601
  constants as fsConstants,
11536
- readFileSync as readFileSync3,
11602
+ readFileSync as readFileSync4,
11537
11603
  renameSync as renameSync2,
11538
11604
  statSync as statSync2,
11539
11605
  writeFileSync as writeFileSync2
@@ -11643,9 +11709,9 @@ function getWinningConfig(directory, surface) {
11643
11709
  let winner = null;
11644
11710
  for (const path of getSurfaceConfigPaths(directory, surface)) {
11645
11711
  try {
11646
- if (!existsSync5(path))
11712
+ if (!existsSync6(path))
11647
11713
  continue;
11648
- const parsed = parseJsonConfig(readFileSync3(path, "utf-8"));
11714
+ const parsed = parseJsonConfig(readFileSync4(path, "utf-8"));
11649
11715
  const entries = getPluginEntries(parsed);
11650
11716
  if (entries.some((entry) => entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`))) {
11651
11717
  if (winner) {
@@ -11678,9 +11744,9 @@ function findPackageJsonUp(startPath) {
11678
11744
  let dir = stat.isDirectory() ? startPath : dirname3(startPath);
11679
11745
  for (let i = 0;i < 10; i++) {
11680
11746
  const pkgPath = join3(dir, "package.json");
11681
- if (existsSync5(pkgPath)) {
11747
+ if (existsSync6(pkgPath)) {
11682
11748
  try {
11683
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync3(pkgPath, "utf-8")));
11749
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync4(pkgPath, "utf-8")));
11684
11750
  if (pkg.success && pkg.data.name === PACKAGE_NAME)
11685
11751
  return pkgPath;
11686
11752
  } catch {}
@@ -11696,9 +11762,9 @@ function findPackageJsonUp(startPath) {
11696
11762
  function getLocalDevPath(directory) {
11697
11763
  for (const configPath of getConfigPaths(directory)) {
11698
11764
  try {
11699
- if (!existsSync5(configPath))
11765
+ if (!existsSync6(configPath))
11700
11766
  continue;
11701
- const rawConfig = parseJsonConfig(readFileSync3(configPath, "utf-8"));
11767
+ const rawConfig = parseJsonConfig(readFileSync4(configPath, "utf-8"));
11702
11768
  const plugins = getPluginEntries(rawConfig);
11703
11769
  for (const entry of plugins) {
11704
11770
  if (entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`))
@@ -11708,7 +11774,7 @@ function getLocalDevPath(directory) {
11708
11774
  const pkgPath = findPackageJsonUp(localPath);
11709
11775
  if (!pkgPath)
11710
11776
  continue;
11711
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync3(pkgPath, "utf-8")));
11777
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync4(pkgPath, "utf-8")));
11712
11778
  if (pkg.success && pkg.data.name === PACKAGE_NAME)
11713
11779
  return localPath;
11714
11780
  }
@@ -11725,7 +11791,7 @@ function getLocalDevVersion(directory) {
11725
11791
  const pkgPath = findPackageJsonUp(localPath);
11726
11792
  if (!pkgPath)
11727
11793
  return null;
11728
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync3(pkgPath, "utf-8")));
11794
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync4(pkgPath, "utf-8")));
11729
11795
  return pkg.success ? pkg.data.version ?? null : null;
11730
11796
  } catch {
11731
11797
  return null;
@@ -11782,7 +11848,7 @@ function preflightConfigUpdates(paths, version) {
11782
11848
  for (const path of paths) {
11783
11849
  try {
11784
11850
  accessSync(path, fsConstants.W_OK);
11785
- const original = readFileSync3(path, "utf-8");
11851
+ const original = readFileSync4(path, "utf-8");
11786
11852
  const updated = configUpdateContent(original, version);
11787
11853
  if (!updated)
11788
11854
  return null;
@@ -11811,7 +11877,7 @@ function restoreConfigs(snapshots) {
11811
11877
  function configsHaveSpec(paths, spec) {
11812
11878
  return paths.every((path) => {
11813
11879
  try {
11814
- const parsed = parseJsonConfig(readFileSync3(path, "utf-8"));
11880
+ const parsed = parseJsonConfig(readFileSync4(path, "utf-8"));
11815
11881
  return getPluginEntries(parsed).includes(spec);
11816
11882
  } catch {
11817
11883
  return false;
@@ -11901,9 +11967,9 @@ function getCachedVersion(_spec) {
11901
11967
  const candidates = [getCurrentRuntimePackageJsonPath()].filter(isString);
11902
11968
  for (const packageJsonPath of candidates) {
11903
11969
  try {
11904
- if (!existsSync5(packageJsonPath))
11970
+ if (!existsSync6(packageJsonPath))
11905
11971
  continue;
11906
- const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync3(packageJsonPath, "utf-8")));
11972
+ const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync4(packageJsonPath, "utf-8")));
11907
11973
  if (pkg.success && pkg.data.version) {
11908
11974
  if (!_spec)
11909
11975
  cachedPackageVersion = pkg.data.version;
@@ -11945,7 +12011,6 @@ async function getLatestVersion(channel = "latest", options = {}) {
11945
12011
  // src/hooks/auto-update-checker/index.ts
11946
12012
  var DEFAULT_CHECK_INTERVAL_MS = 60 * 60 * 1000;
11947
12013
  var DEFAULT_INIT_DELAY_MS = 5000;
11948
- var TIMESTAMP_FILENAME = "last-update-check.json";
11949
12014
  var PENDING_UPDATE_FILENAME = "pending-plugin-update.json";
11950
12015
  var PENDING_UPDATE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
11951
12016
  function warn2(message) {
@@ -11997,34 +12062,50 @@ async function maybeRunCheck(ctx, options) {
11997
12062
  }
11998
12063
  await runStartupCheck(ctx, options);
11999
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
+ }
12000
12075
  function claimCheckSlot(storageDir, intervalMs) {
12001
12076
  if (!storageDir)
12002
12077
  return true;
12003
12078
  try {
12004
- const file = join4(storageDir, TIMESTAMP_FILENAME);
12005
- if (existsSync6(file)) {
12006
- try {
12007
- const raw = JSON.parse(readFileSync4(file, "utf-8"));
12008
- const last = typeof raw.lastCheckedMs === "number" ? raw.lastCheckedMs : 0;
12009
- if (Number.isFinite(last) && Date.now() - last < intervalMs) {
12010
- return false;
12011
- }
12012
- } catch {}
12013
- }
12014
- mkdirSync(dirname4(file), { recursive: true });
12015
- const tmp = `${file}.tmp.${process.pid}`;
12016
- writeFileSync3(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
12017
- 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() });
12018
12085
  return true;
12019
12086
  } catch (err) {
12020
12087
  warn2(`[auto-update-checker] Could not coordinate via timestamp file: ${String(err)}`);
12021
12088
  return true;
12022
12089
  }
12023
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
+ }
12024
12104
  function pendingMarkerPath(storageDir) {
12025
12105
  return join4(storageDir ?? getOpenCodeStorageDir(), PENDING_UPDATE_FILENAME);
12026
12106
  }
12027
12107
  function writePendingUpdateMarker(storageDir, update, version) {
12108
+ recordUpdaterPinnedSpec(storageDir, update.spec);
12028
12109
  try {
12029
12110
  const dir = storageDir ?? dirname4(pendingMarkerPath(storageDir));
12030
12111
  mkdirSync(dir, { recursive: true });
@@ -12039,10 +12120,10 @@ function writePendingUpdateMarker(storageDir, update, version) {
12039
12120
  }
12040
12121
  function consumePendingUpdateMarker(storageDir, loadedVersion) {
12041
12122
  const path = pendingMarkerPath(storageDir);
12042
- if (!existsSync6(path))
12123
+ if (!existsSync7(path))
12043
12124
  return;
12044
12125
  try {
12045
- const marker = JSON.parse(readFileSync4(path, "utf-8"));
12126
+ const marker = JSON.parse(readFileSync5(path, "utf-8"));
12046
12127
  const age = Date.now() - (marker.writtenAt ?? 0);
12047
12128
  const olderThanLoaded = typeof marker.version === "string" && typeof loadedVersion === "string" && (compareSemverCore(marker.version, loadedVersion) ?? 0) < 0;
12048
12129
  if (marker.version === loadedVersion && marker.spec === `${PACKAGE_NAME}@${loadedVersion}` && Array.isArray(marker.configPaths) && marker.configPaths.length === 2) {
@@ -12114,11 +12195,15 @@ async function runBackgroundUpdateCheck(ctx, options) {
12114
12195
  return;
12115
12196
  }
12116
12197
  log(`[auto-update-checker] Update available (${channel}): ${currentVersion} → ${latestVersion}`);
12117
- if (pluginInfo.isPinned) {
12118
- showToast(ctx, `Magic Context ${latestVersion}`, `v${latestVersion} available. Version is pinned; update your OpenCode plugin config to upgrade.`, "info", 8000);
12119
- 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");
12120
12202
  return;
12121
12203
  }
12204
+ if (updaterOwnedPin) {
12205
+ log("[auto-update-checker] Version was pinned by the updater; continuing auto-update");
12206
+ }
12122
12207
  if (!options.autoUpdate) {
12123
12208
  showToast(ctx, `Magic Context ${latestVersion}`, `v${latestVersion} available. Auto-update is disabled.`, "info", 8000);
12124
12209
  log("[auto-update-checker] Auto-update disabled, notification only");
@@ -12452,9 +12537,11 @@ class SubcModuleTransport {
12452
12537
  this.wrapupSessions.delete(args.sessionId);
12453
12538
  };
12454
12539
  const laneDeadlineMs = Date.now() + attemptTimeoutMs;
12455
- let releaseLane;
12540
+ let releaseLane = () => {};
12456
12541
  try {
12457
- releaseLane = await this.acquireCorrectnessLane(args.sessionId, args.signal, laneDeadlineMs);
12542
+ if (!args.bypassSessionLane) {
12543
+ releaseLane = await this.acquireCorrectnessLane(args.sessionId, args.signal, laneDeadlineMs);
12544
+ }
12458
12545
  } catch (error) {
12459
12546
  finishWrapupTracking();
12460
12547
  if (args.method === "state_sync" && isDeadlineFailure(error)) {
@@ -12556,12 +12643,13 @@ class SubcModuleTransport {
12556
12643
  args.onTimings?.(timings);
12557
12644
  }
12558
12645
  }
12559
- async authorityRequest(sessionId, projectRoot, method, body) {
12646
+ async authorityRequest(sessionId, projectRoot, method, body, timeoutMs) {
12560
12647
  const response = await this.call({
12561
12648
  sessionId,
12562
12649
  projectRoot,
12563
12650
  method,
12564
- body: { ...body, method, v: 1 }
12651
+ body: { ...body, method, v: 1 },
12652
+ timeoutMs
12565
12653
  });
12566
12654
  if (isRecord(response) && isRecord(response.result))
12567
12655
  return response.result;
@@ -12621,6 +12709,20 @@ class SubcModuleTransport {
12621
12709
  throw new Error("mirror.pull omitted page");
12622
12710
  return { page: response.page };
12623
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
+ }
12624
12726
  async deleteSession(sessionId, projectRoot) {
12625
12727
  await this.call({
12626
12728
  sessionId,
@@ -13631,7 +13733,7 @@ function recordRetrospectiveWindowProcessed(db, projectPath, windowKey) {
13631
13733
 
13632
13734
  // src/features/magic-context/dreamer/task-executor.ts
13633
13735
  import { createHash as createHash8 } from "node:crypto";
13634
- import { existsSync as existsSync9 } from "node:fs";
13736
+ import { existsSync as existsSync10 } from "node:fs";
13635
13737
 
13636
13738
  // src/agents/dreamer.ts
13637
13739
  var DREAMER_AGENT = "dreamer";
@@ -17135,7 +17237,7 @@ async function archiveExpiredMemories(args) {
17135
17237
  }
17136
17238
 
17137
17239
  // src/features/magic-context/dreamer/maintain-docs-protected-enforcement.ts
17138
- 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";
17139
17241
  import { join as join7 } from "node:path";
17140
17242
 
17141
17243
  // src/features/magic-context/dreamer/protected-regions.ts
@@ -17239,8 +17341,8 @@ function snapshotMaintainDocsFiles(docsDir) {
17239
17341
  for (const name of MAINTAIN_DOCS_SNAPSHOT_FILES) {
17240
17342
  const path = join7(docsDir, name);
17241
17343
  try {
17242
- if (existsSync7(path)) {
17243
- snapshot.set(name, readFileSync5(path, "utf8"));
17344
+ if (existsSync8(path)) {
17345
+ snapshot.set(name, readFileSync6(path, "utf8"));
17244
17346
  }
17245
17347
  } catch {}
17246
17348
  }
@@ -17250,7 +17352,7 @@ function enforceMaintainDocsProtectedRegions(args) {
17250
17352
  for (const [fileName, original] of args.snapshot) {
17251
17353
  const path = join7(args.docsDir, fileName);
17252
17354
  try {
17253
- const current = readFileSync5(path, "utf8");
17355
+ const current = readFileSync6(path, "utf8");
17254
17356
  const { text, violated } = enforceProtectedRegions(original, current);
17255
17357
  if (!violated) {
17256
17358
  continue;
@@ -17267,7 +17369,7 @@ function enforceMaintainDocsProtectedRegions(args) {
17267
17369
  import { createHash as createHash6 } from "node:crypto";
17268
17370
 
17269
17371
  // src/features/magic-context/dreamer/map-memories-prompt.ts
17270
- import { existsSync as existsSync8, statSync as statSync3 } from "node:fs";
17372
+ import { existsSync as existsSync9, statSync as statSync3 } from "node:fs";
17271
17373
  import path3 from "node:path";
17272
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.
17273
17375
 
@@ -17305,7 +17407,7 @@ function extractMemoryCandidatePaths(content, repoDir) {
17305
17407
  if (!abs.startsWith(`${root}/`))
17306
17408
  continue;
17307
17409
  try {
17308
- if (existsSync8(abs) && statSync3(abs).isFile())
17410
+ if (existsSync9(abs) && statSync3(abs).isFile())
17309
17411
  found.add(rel);
17310
17412
  } catch {}
17311
17413
  if (found.size >= MAX_SEED_PATHS_PER_MEMORY)
@@ -20066,8 +20168,8 @@ async function runAgenticTask(config, ctx, helpers) {
20066
20168
  const lastRunAt = getTaskScheduleState(db, projectIdentity, config.task)?.lastRunAt ?? null;
20067
20169
  const maintainDocsSnapshot = task === "maintain-docs" ? snapshotMaintainDocsFiles(docsDir) : undefined;
20068
20170
  const existingDocs = task === "maintain-docs" ? {
20069
- architecture: existsSync9(`${docsDir}/ARCHITECTURE.md`),
20070
- structure: existsSync9(`${docsDir}/STRUCTURE.md`)
20171
+ architecture: existsSync10(`${docsDir}/ARCHITECTURE.md`),
20172
+ structure: existsSync10(`${docsDir}/STRUCTURE.md`)
20071
20173
  } : undefined;
20072
20174
  const abortController = new AbortController;
20073
20175
  let leaseLost = false;
@@ -20293,11 +20395,11 @@ function planDueTasks(db, projectIdentity, tasks, now) {
20293
20395
  }
20294
20396
  return due;
20295
20397
  }
20296
- function advanceAfterRun(db, projectIdentity, due, finishedAt, status, error, schedulePatch) {
20398
+ function advanceAfterRun(db, projectIdentity, due, finishedAt, status, error, schedulePatch, startedAt) {
20297
20399
  writeTaskScheduleState(db, {
20298
20400
  projectPath: projectIdentity,
20299
20401
  task: due.config.task,
20300
- lastRunAt: status === "completed" ? finishedAt : readLastRunAt(db, projectIdentity, due.config.task),
20402
+ lastRunAt: status === "completed" ? startedAt ?? finishedAt : readLastRunAt(db, projectIdentity, due.config.task),
20301
20403
  nextDueAt: nextDueAtMs(due.config.schedule, finishedAt, due.scheduledAt),
20302
20404
  schedule: due.config.schedule,
20303
20405
  lastStatus: status,
@@ -20382,6 +20484,7 @@ async function runDomainGroup(deps, group, cb) {
20382
20484
  }
20383
20485
  }
20384
20486
  let outcome;
20487
+ const startedAt = Date.now();
20385
20488
  try {
20386
20489
  outcome = await executor(due.config, {
20387
20490
  db,
@@ -20395,7 +20498,7 @@ async function runDomainGroup(deps, group, cb) {
20395
20498
  }
20396
20499
  const finishedAt = Date.now();
20397
20500
  if (outcome.status === "completed") {
20398
- advanceAfterRun(db, projectIdentity, due, finishedAt, "completed", null, outcome.schedulePatch);
20501
+ advanceAfterRun(db, projectIdentity, due, finishedAt, "completed", null, outcome.schedulePatch, startedAt);
20399
20502
  cb?.onRan?.(due.config.task, outcome.detail, outcome.backlog);
20400
20503
  } else if (outcome.transient) {
20401
20504
  recordTransientFailure(db, projectIdentity, due, finishedAt, outcome.error ?? null);
@@ -21523,6 +21626,8 @@ async function ensureProjectRegisteredFromOpenCodeDirectory(directory, db) {
21523
21626
  registerProjectEmbedding(db, projectIdentity, routing.primary, features, directory);
21524
21627
  if (routing.shadow) {
21525
21628
  registerProjectShadowEmbedding(db, projectIdentity, routing.shadow, directory);
21629
+ } else {
21630
+ unregisterProjectShadowEmbedding(projectIdentity);
21526
21631
  }
21527
21632
  }
21528
21633
 
@@ -22224,6 +22329,56 @@ function createTagger() {
22224
22329
  };
22225
22330
  }
22226
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
+
22227
22382
  // src/plugin/rpc-handlers.ts
22228
22383
  import { randomUUID as randomUUID4 } from "node:crypto";
22229
22384
  import { once } from "node:events";
@@ -27114,11 +27269,35 @@ function toFlatModuleWireBody(payload) {
27114
27269
  function moduleWireBodyBytes(payload) {
27115
27270
  return Buffer.byteLength(JSON.stringify(toFlatModuleWireBody(payload)));
27116
27271
  }
27117
- function buildPagedModuleTransformPayloads(body, pageMaxBytes = MODULE_PAGE_MAX_BYTES) {
27272
+ function buildPagedModuleTransformPayloads(body, pageMaxBytes = MODULE_PAGE_MAX_BYTES, forcePageEnvelope = false) {
27118
27273
  const serializedBody = JSON.stringify(body);
27119
27274
  const unpagedBytes = Buffer.byteLength(serializedBody);
27120
- if (unpagedBytes <= pageMaxBytes)
27121
- 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
+ }
27122
27301
  const arrayFields = [
27123
27302
  "input",
27124
27303
  "messages",
@@ -27768,36 +27947,43 @@ function canonicalOrdinalForMessageId(args) {
27768
27947
  args.state.idOrdinalMemo.set(args.messageId, canonical);
27769
27948
  return canonical;
27770
27949
  }
27771
- function serializeCompartment(args) {
27950
+ var boundaryDiagnosticObserverForTest = null;
27951
+ function logBoundaryDiagnostic(sessionId, message) {
27952
+ sessionLog(sessionId, message);
27953
+ boundaryDiagnosticObserverForTest?.(message);
27954
+ }
27955
+ function readCompartmentBoundaryResolution(args) {
27772
27956
  const startRaw = args.readRawById(args.compartment.startMessageId);
27773
27957
  const endRaw = args.readRawById(args.compartment.endMessageId);
27774
- const startOrdinal = canonicalOrdinalForMessageId({
27775
- sessionId: args.sessionId,
27776
- raw: startRaw,
27777
- messageId: args.compartment.startMessageId,
27778
- generation: args.state.moduleGeneration,
27779
- state: args.state
27780
- });
27781
- const endOrdinal = canonicalOrdinalForMessageId({
27782
- sessionId: args.sessionId,
27783
- raw: endRaw,
27784
- messageId: args.compartment.endMessageId,
27785
- generation: args.state.moduleGeneration,
27786
- state: args.state
27787
- });
27788
- if (startOrdinal === "mismatch" || endOrdinal === "mismatch")
27789
- return "mismatch";
27790
- if (startOrdinal === null || endOrdinal === null)
27791
- return null;
27792
- const startCreatedAt = startRaw?.createdAt;
27793
- 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;
27794
27980
  const dateRange = typeof startCreatedAt === "number" && typeof endCreatedAt === "number" ? { start_date: formatDate(startCreatedAt), end_date: formatDate(endCreatedAt) } : {};
27795
27981
  return {
27796
27982
  sequence: args.compartment.sequence,
27797
- start_message: startOrdinal,
27798
- end_message: endOrdinal,
27799
- start_message_id: flatBlockIdForRawMessage(args.compartment.startMessageId, startRaw, "start"),
27800
- 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"),
27801
27987
  ...dateRange,
27802
27988
  title: args.compartment.title,
27803
27989
  content: args.compartment.content,
@@ -27811,6 +27997,15 @@ function serializeCompartment(args) {
27811
27997
  created_at: args.compartment.createdAt
27812
27998
  };
27813
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
+ }
27814
28009
  function seedBoundaryFromSerializedCompartments(compartments) {
27815
28010
  const serialized = compartments.filter(isRecord).filter((compartment) => typeof compartment.sequence === "number" && typeof compartment.end_message_id === "string");
27816
28011
  serialized.sort((left, right) => left.sequence - right.sequence);
@@ -28283,21 +28478,102 @@ async function buildModuleStateSyncPayload(args) {
28283
28478
  const compartments = [];
28284
28479
  let serializedCount = 0;
28285
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) : [];
28286
- for (const compartment of compartmentsToSerialize) {
28287
- args.options?.beforeSerializeCompartment?.();
28288
- if (args.options?.shouldAbortSeed?.())
28289
- return "seed_budget";
28290
- 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({
28291
28487
  compartment,
28292
28488
  sessionId: args.pass.sessionId,
28293
28489
  readRawById,
28294
28490
  state: args.state
28295
28491
  });
28296
- 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") {
28297
28514
  return "mismatch";
28298
- if (serialized === null)
28299
- return "unresolved";
28300
- 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
+ }));
28301
28577
  serializedCount += 1;
28302
28578
  const yieldEvery = Math.max(1, args.options?.yieldEveryCompartments ?? 10);
28303
28579
  if (serializedCount % yieldEvery === 0) {
@@ -29691,8 +29967,8 @@ function registerRpcHandlers(rpcServer, args) {
29691
29967
  const db = getDb();
29692
29968
  if (!db)
29693
29969
  return { ok: false, error: "db unavailable" };
29694
- const { runManagedRecomp } = await import("./recomp-orchestrator-k20sfpm2.js");
29695
- 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");
29696
29972
  log(`[rpc] recomp requested for session ${sessionId}`);
29697
29973
  const ctx = await buildManagedCtx(db);
29698
29974
  runManagedRecomp(ctx, sessionId).then((message) => {
@@ -29710,8 +29986,8 @@ function registerRpcHandlers(rpcServer, args) {
29710
29986
  const db = getDb();
29711
29987
  if (!db)
29712
29988
  return { ok: false, error: "db unavailable" };
29713
- const { runManagedUpgrade } = await import("./recomp-orchestrator-k20sfpm2.js");
29714
- 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");
29715
29991
  log(`[rpc] session-upgrade requested for session ${sessionId}`);
29716
29992
  const ctx = await buildManagedCtx(db);
29717
29993
  runManagedUpgrade(ctx, sessionId).then((message) => {
@@ -29727,7 +30003,7 @@ function registerRpcHandlers(rpcServer, args) {
29727
30003
  if (!db)
29728
30004
  return { ok: false, error: "db unavailable" };
29729
30005
  try {
29730
- const { updateSessionMeta } = await import("./storage-meta-session-x3j0q9qy.js");
30006
+ const { updateSessionMeta } = await import("./storage-meta-session-z818pn1e.js");
29731
30007
  updateSessionMeta(db, sessionId, { upgradeRemindedAt: Date.now() });
29732
30008
  return { ok: true };
29733
30009
  } catch (error) {
@@ -30243,6 +30519,31 @@ function releaseClaimWithoutDelivery(db, sessionId, claimToken) {
30243
30519
  sessionLog(sessionId, "channel2 terminal-run claim release failed; lease will heal:", error);
30244
30520
  }
30245
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
+ }
30246
30547
  async function maybeDeliverChannel2(sessionId, deps) {
30247
30548
  let state;
30248
30549
  try {
@@ -30281,36 +30582,25 @@ async function maybeDeliverChannel2(sessionId, deps) {
30281
30582
  return false;
30282
30583
  }
30283
30584
  try {
30284
- const promptContext = await resolvePromptContext2(client, sessionId);
30285
30585
  const reminder = deps.directiveText ?? buildChannel2Reminder(effectiveU, reclaimableToolOutputCount(deps.baseline?.baselineParts ?? []), deps.oldestReclaimableToolTags);
30286
- const body = {
30287
- noReply: false,
30288
- parts: [{ type: "text", text: reminder, synthetic: true }]
30289
- };
30290
- if (promptContext?.agent)
30291
- body.agent = promptContext.agent;
30292
- if (promptContext?.model) {
30293
- body.model = {
30294
- providerID: promptContext.model.providerID,
30295
- modelID: promptContext.model.modelID
30296
- };
30297
- }
30298
- if (promptContext?.variant)
30299
- body.variant = promptContext.variant;
30300
- const session = client.session;
30301
- if (typeof session?.promptAsync !== "function") {
30302
- throw new Error("client has no session.promptAsync");
30303
- }
30304
- const claim = getChannel2NudgeClaim(deps.db, sessionId);
30305
- if (claim.state !== "claimed" || claim.claimToken !== claimToken) {
30306
- sessionLog(sessionId, `channel2 ceiling nudge delivery skipped: claim no longer owned before send (state=${claim.state || "empty"})`);
30307
- return false;
30308
- }
30309
- if (!subagentRunIsActive(deps, sessionId)) {
30310
- 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)
30311
30603
  return false;
30312
- }
30313
- await session.promptAsync({ path: { id: sessionId }, body });
30314
30604
  } catch (error) {
30315
30605
  try {
30316
30606
  const restored = casChannel2NudgeClaim(deps.db, sessionId, "pending", claimToken);
@@ -30346,7 +30636,7 @@ function canConsumeDeferredOnThisPass(args) {
30346
30636
  return args.schedulerDecision === "execute" || args.contextPercentage >= (args.forceMaterializationPercentage ?? escalationBands(65).forceMaterializationPercentage);
30347
30637
  }
30348
30638
  function hasReclaimRide(signals) {
30349
- return signals.hardFold || signals.force || signals.explicitFlush || signals.publishedHistory || signals.agentDrop;
30639
+ return signals.hardFold || signals.force || signals.explicitFlush || signals.publishedHistory;
30350
30640
  }
30351
30641
 
30352
30642
  // src/hooks/magic-context/caveman-cleanup.ts
@@ -30468,7 +30758,7 @@ function replayCavemanCompression(sessionId, db, targets, tags) {
30468
30758
  }
30469
30759
 
30470
30760
  // src/hooks/magic-context/compaction-off-transition.ts
30471
- import { existsSync as existsSync11 } from "node:fs";
30761
+ import { existsSync as existsSync12 } from "node:fs";
30472
30762
  var loggedUnverifiedMarkerCleanupRetry = false;
30473
30763
  var COMPACTION_OFF_FLIP_NOTICE = [
30474
30764
  "## Magic Context — compaction-off mode is now active",
@@ -30503,7 +30793,7 @@ function clearCachedM0Baseline(db, sessionId) {
30503
30793
  return (result.changes ?? 0) > 0;
30504
30794
  }
30505
30795
  function cleanupOffMarkers(sessionId) {
30506
- if (!existsSync11(getOpenCodeDbPath())) {
30796
+ if (!existsSync12(getOpenCodeDbPath())) {
30507
30797
  return { verified: true, removedLineages: 0, removedRows: 0, retainedLineages: 0 };
30508
30798
  }
30509
30799
  return removeMcOwnedCompactionMarkers(sessionId, MARKER_SUMMARY_TEXT);
@@ -31440,7 +31730,7 @@ function createPassOutcome() {
31440
31730
  }
31441
31731
 
31442
31732
  // src/hooks/magic-context/rust-mode-transform.ts
31443
- import { createHash as createHash12 } from "node:crypto";
31733
+ import { createHash as createHash12, randomUUID as randomUUID6 } from "node:crypto";
31444
31734
 
31445
31735
  // src/hooks/magic-context/raw-fallback-context-limit.ts
31446
31736
  class RawFallbackContextLimitError extends Error {
@@ -33956,7 +34246,8 @@ async function runPostTransformPhase(args) {
33956
34246
  const firstRenderBust = m0M1EnabledForFold && !completeCachedPrefixAvailable;
33957
34247
  let foldExecutedThisPass = false;
33958
34248
  let publishedM1RefreshedThisPass = false;
33959
- const softRefreshOpportunity = args.schedulerDecision === "execute";
34249
+ let prefixPreflightFailed = false;
34250
+ const softRefreshOpportunity = args.schedulerDecision === "execute" || deferredMaterialize;
33960
34251
  let m0RematerializedThisPass = false;
33961
34252
  const m0CoverageBeforeFold = args.sessionMeta.cachedM0Bytes === null ? -1 : args.sessionMeta.cachedM0MaxCompartmentSeq;
33962
34253
  let m0MaterializeReason = null;
@@ -33983,6 +34274,7 @@ async function runPostTransformPhase(args) {
33983
34274
  compactionOff
33984
34275
  });
33985
34276
  preparedPrefix = foldResult;
34277
+ prefixPreflightFailed = foldResult.materializationContentionRetryExhausted === true;
33986
34278
  foldExecutedThisPass = foldExecutesThisPass(foldDueDecision.value || softRefreshOpportunity, foldResult.m0RematerializedThisPass);
33987
34279
  publishedM1RefreshedThisPass = !foldResult.materializationContentionRetryExhausted && previousM1 !== args.sessionMeta.cachedM1Bytes?.toString("utf8");
33988
34280
  m0RematerializedThisPass = foldResult.m0RematerializedThisPass;
@@ -34004,29 +34296,29 @@ async function runPostTransformPhase(args) {
34004
34296
  }
34005
34297
  } catch (error) {
34006
34298
  preparedPrefix = cachedPrefixBeforePreflight;
34299
+ prefixPreflightFailed = true;
34007
34300
  args.passOutcome?.record("m0-m1-fold-preexecution-degradation");
34008
34301
  sessionLog(args.sessionId, "transform: m[0] HARD fold pre-execution failed:", getErrorMessage(error));
34009
34302
  }
34010
34303
  sessionLog(args.sessionId, `m[0] HARD fold decision: reason=${foldDueDecision.reason ?? "unknown"} executed=${foldExecutedThisPass}`);
34011
34304
  }
34012
- const publishedWorkDrainAllowed = args.schedulerDecision === "execute" || materializationRequested || forceMaterialization || emergencyDropEligible || foldExecutedThisPass || firstRenderBust;
34013
- const shouldReadPendingOps = !compactionOff && (materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || foldExecutedThisPass || firstRenderBust || compartmentRunning);
34305
+ const shouldReadPendingOps = !compactionOff && (materializationRequested || publishedM1RefreshedThisPass || args.schedulerDecision === "execute" || forceMaterialization || foldExecutedThisPass || firstRenderBust || compartmentRunning);
34014
34306
  const pendingOps = shouldReadPendingOps ? getPendingOps(args.db, args.sessionId) : [];
34015
34307
  const hasPendingUserOps = pendingOps.length > 0;
34016
34308
  const formatPendingOpsDepth = () => {
34017
34309
  const depth = getPendingOpsCount(args.db, args.sessionId);
34018
34310
  return depth === null ? "not loaded (deferred pass)" : String(depth);
34019
34311
  };
34020
- const shouldApplyPendingOps = !compactionOff && (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization || foldExecutedThisPass || firstRenderBust) && publishedWorkDrainAllowed;
34021
34312
  const rideSignals = {
34022
34313
  hardFold: foldExecutedThisPass || firstRenderBust,
34023
- force: forceMaterialization || emergencyDropEligible,
34024
- explicitFlush: isExplicitFlush,
34025
- publishedHistory: publishedM1RefreshedThisPass || !m0M1EnabledForFold && (args.pendingCompartmentInjection?.compartmentCount ?? 0) > 0 && (args.historyRebuiltThisPass || args.rebuiltHistoryFromInitialPrepare || args.canConsumeDeferredLate && args.deferredHistoryWasPendingAtPassStart),
34026
- agentDrop: false
34027
- };
34028
- let shouldRunHeuristics = !compactionOff && hasReclaimRide(rideSignals) && (rideSignals.publishedHistory || materializationRequested || forceMaterialization || foldExecutedThisPass || firstRenderBust || emergencyDropEligible || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
34029
- 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;
34030
34322
  if (isCacheBustingPass && args.client && args.ctxReduceAvailability.callable && !hasLoggedCtxReducePermissionDeny(args.sessionId)) {
34031
34323
  try {
34032
34324
  const denied = await resolveToolPermissionDenied(args.client, args.sessionId, "ctx_reduce", args.activeAgent);
@@ -34052,13 +34344,8 @@ async function runPostTransformPhase(args) {
34052
34344
  if (!shouldApplyPendingOps && args.schedulerDeferReason !== undefined && args.schedulerDeferReason !== null) {
34053
34345
  sessionLog(args.sessionId, `pending ops WILL NOT APPLY — reason=${args.schedulerDeferReason} pendingOps=${formatPendingOpsDepth()} context=${args.contextUsage.percentage.toFixed(1)}%`);
34054
34346
  }
34055
- if (compartmentRunning && hasPendingUserOps) {
34056
- if (publishedWorkDrainAllowed) {
34057
- const bypassReason = forceMaterialization ? `emergency >=${args.forceMaterializationPercentage}%` : "m0 hard fold";
34058
- sessionLog(args.sessionId, `transform: compartment-gate bypass (${bypassReason}) — applying ${pendingOps.length} pending ops while compartment agent runs (${args.contextUsage.percentage.toFixed(1)}%)`);
34059
- } else {
34060
- sessionLog(args.sessionId, "transform: deferring pending ops — compartment agent in progress");
34061
- }
34347
+ if (hasPendingUserOps && !shouldApplyPendingOps) {
34348
+ sessionLog(args.sessionId, `pending ops held — reason=no originating cache-bust opportunity; scheduler=${args.schedulerDecision}, historianRunning=${compartmentRunning}`);
34062
34349
  }
34063
34350
  let explicitMaterializedSuccessfully = false;
34064
34351
  let deferredMaterializedSuccessfully = false;
@@ -34090,9 +34377,6 @@ async function runPostTransformPhase(args) {
34090
34377
  const tApply = performance.now();
34091
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));
34092
34379
  if (pendingOpsDidMutate) {
34093
- rideSignals.agentDrop = true;
34094
- isCacheBustingPass = hasReclaimRide(rideSignals);
34095
- shouldRunHeuristics = isCacheBustingPass;
34096
34380
  droppedCount += pendingOps.length;
34097
34381
  for (const pendingOp of pendingOps) {
34098
34382
  const message = args.targets.get(pendingOp.tagId)?.message;
@@ -34854,6 +35138,8 @@ var RUST_PARK_RETRY_INTERVAL = 5;
34854
35138
  var RUST_EMERGENCY_WALL_PCT = 95;
34855
35139
  var RUST_PARK_PROBE_PRESSURE_BYPASS_PCT = 90;
34856
35140
  var RUST_SEND_TIMEOUT_MS = 15000;
35141
+ var RUST_SILENT_RESEND_AFTER_MS = 1e4;
35142
+ var RUST_HEALTH_PROBE_TIMEOUT_MS = 2000;
34857
35143
  var RUST_LKG_FROZEN_HEALTHY_PASS_LIMIT = 8;
34858
35144
  var RUST_LKG_FROZEN_RAW_TAIL_GROWTH_LIMIT = 16;
34859
35145
  function activeAgentFromMessages(messages) {
@@ -35763,6 +36049,64 @@ function createRustModeTransform(deps, options) {
35763
36049
  clearTimeout(timer);
35764
36050
  }
35765
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
+ };
35766
36110
  const markFailure = (sessionId, state, error) => {
35767
36111
  state.consecutiveFailures = isNonRetryableStateSyncFailure(error) ? Math.max(RUST_FAILURE_PARK_THRESHOLD, state.consecutiveFailures + 1) : state.consecutiveFailures + 1;
35768
36112
  state.failureCount += 1;
@@ -35979,6 +36323,8 @@ function createRustModeTransform(deps, options) {
35979
36323
  } catch {}
35980
36324
  let appliedAt;
35981
36325
  let emergencyFailClosed = false;
36326
+ let providerProvenEmergency = false;
36327
+ let recoveryProjectRoot = options.projectRoot ?? deps.directory ?? "";
35982
36328
  const passUsageSnapshot = loadContextUsage(deps.contextUsageMap, deps.db, sessionId);
35983
36329
  requestInputTokens = Math.max(0, Math.floor(passUsageSnapshot.inputTokens));
35984
36330
  let preflightError;
@@ -36012,11 +36358,15 @@ function createRustModeTransform(deps, options) {
36012
36358
  }
36013
36359
  const transformGeometry = transformGeometryForWire(resolvedWindowGeometry);
36014
36360
  const hasTrustedEmergencyWall = transformGeometry ? transformGeometry.usable_hard > 0 : resolvedContextLimit !== undefined && resolvedContextLimit > 0;
36015
- 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;
36016
36364
  if (overflowState) {
36017
36365
  const detectedLimitMatchesModel = overflowState.detectedContextLimitModelKey === null || canonicalModelIdentity(overflowState.detectedContextLimitModelKey) === canonicalModelIdentity(modelKey ?? "");
36018
36366
  const hasProviderProof = overflowState.detectedContextLimit > 0 && detectedLimitMatchesModel || isProviderOverflowReconfirmed(sessionId);
36019
- 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);
36020
36370
  }
36021
36371
  const serveRawFallback = (cause) => {
36022
36372
  const contextLimit = transformGeometry?.usable_hard ?? resolvedContextLimit ?? (overflowState && overflowState.detectedContextLimit > 0 ? overflowState.detectedContextLimit : undefined);
@@ -36319,6 +36669,7 @@ function createRustModeTransform(deps, options) {
36319
36669
  nowMs: Date.now()
36320
36670
  };
36321
36671
  const projectRoot = options.projectRoot ?? directory;
36672
+ recoveryProjectRoot = projectRoot;
36322
36673
  const authoritySeqAdoption = { used: false };
36323
36674
  const memorySyncRequested = options.memorySyncRequestedSessions?.delete(sessionId) === true;
36324
36675
  const currentStateSyncInputSignature = stateSyncInputSignature({
@@ -36513,7 +36864,7 @@ function createRustModeTransform(deps, options) {
36513
36864
  logStage(sessionId, "wireBuild", wireBuildStartedAt, timings, wireDelta ? `mode=tail_delta input=${encodedInput.length}` : "mode=full");
36514
36865
  const sendTransformSeries = async (payload, detail = "") => {
36515
36866
  const pagingStartedAt = performance.now();
36516
- const pages = buildPagedModuleTransformPayloads(payload, options.modulePageMaxBytes);
36867
+ const pages = buildPagedModuleTransformPayloads(payload, options.modulePageMaxBytes, true);
36517
36868
  timings.paging += performance.now() - pagingStartedAt;
36518
36869
  const seedMessageCount = Array.isArray(payload.input) ? payload.input.length : Array.isArray(payload.messages) ? payload.messages.length : 0;
36519
36870
  const paged = pages.some((entry) => typeof entry.page.transform_page_id === "string");
@@ -36524,7 +36875,7 @@ function createRustModeTransform(deps, options) {
36524
36875
  try {
36525
36876
  const attemptClass = paged ? index === pages.length - 1 ? "transform_series_execute" : "transform_page_upload" : undefined;
36526
36877
  const attemptTimeoutMs = options.moduleTimeoutMs ?? (attemptClass === "transform_series_execute" ? transformColdStartExecuteTimeoutMs(seedMessageCount) : attemptClass === "transform_page_upload" ? TRANSFORM_PAGE_UPLOAD_TIMEOUT_MS : timeoutMs);
36527
- moduleResponse = await callModule({
36878
+ const callArgs = {
36528
36879
  sessionId,
36529
36880
  projectRoot,
36530
36881
  method: "transform",
@@ -36535,7 +36886,8 @@ function createRustModeTransform(deps, options) {
36535
36886
  },
36536
36887
  generationSensitive: paged && index > 0,
36537
36888
  attemptClass
36538
- }, attemptTimeoutMs);
36889
+ };
36890
+ moduleResponse = page.transform_page_complete ? await callTransformWithSilentResend(callArgs, attemptTimeoutMs) : await callModule(callArgs, attemptTimeoutMs);
36539
36891
  } catch (error) {
36540
36892
  if (paged && isTransformPageAttemptMismatch(error)) {
36541
36893
  return {
@@ -37061,6 +37413,21 @@ function createRustModeTransform(deps, options) {
37061
37413
  sessionLog(sessionId, "mc_rust_emergency_refusal before_lkg");
37062
37414
  markFailure(sessionId, state, error);
37063
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
+ }
37064
37431
  throw new EmergencyFailClosedError(ENGINE_RECONNECTING_USER_MESSAGE, {
37065
37432
  cause: error
37066
37433
  });
@@ -37557,6 +37924,7 @@ function createTransform(deps) {
37557
37924
  projectRoot: deps.rustModeProjectRoot,
37558
37925
  notifyParked: deps.onRustModeParked,
37559
37926
  onProjectPrepared: deps.onRustModeProjectPrepared,
37927
+ onEngineReconnectRefusal: deps.onRustEngineReconnectRefusal,
37560
37928
  memorySyncRequestedSessions: deps.rustMemorySyncRequestedSessions,
37561
37929
  allowAuthorityProtocolBypassForTests: deps.rustModeAllowAuthorityProtocolBypassForTests
37562
37930
  }) : undefined;
@@ -38975,6 +39343,142 @@ function formatEmbedFailureSummary(embedded, remaining, failure, style = "markdo
38975
39343
  return `${progress} ${renderEmbeddingFailure(failure?.class ?? "empty_result", style)}`;
38976
39344
  }
38977
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
+
38978
39482
  // src/hooks/magic-context/text-complete.ts
38979
39483
  function createTextCompleteHandler() {
38980
39484
  return async (_input, output) => {
@@ -39375,7 +39879,7 @@ function memoryGuidanceBlock(memoryEnabled) {
39375
39879
  ` : "";
39376
39880
  }
39377
39881
  var BASE_INTRO = (memoryEnabled) => `Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
39378
- 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".
39379
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.
39380
39884
  ${CTX_NOTE_GUIDANCE}
39381
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.
@@ -40136,6 +40640,8 @@ function createMagicContextHook(deps) {
40136
40640
  authoritySeed: (args) => transport.authoritySeed(args),
40137
40641
  authorityDrain: (args) => transport.authorityDrain(args),
40138
40642
  mirrorPull: (args) => transport.mirrorPull(args),
40643
+ mirrorMemory: (args) => transport.mirrorMemory(args),
40644
+ memoryIdentityAck: (args) => transport.memoryIdentityAck(args),
40139
40645
  getCompartmentsAfter: async (sessionId, afterSequence) => {
40140
40646
  const response = await transport.call({
40141
40647
  sessionId,
@@ -40166,18 +40672,45 @@ function createMagicContextHook(deps) {
40166
40672
  return client;
40167
40673
  })();
40168
40674
  const rustModeModuleClient = deps.config.transform_mode === "rust" ? authorityRecoveryModuleClient : undefined;
40169
- const syncModuleDomain = async (domain) => {
40675
+ const rustRefusalRecovery = rustModeModuleClient ? createRustRefusalRecovery({
40676
+ moduleClient: rustModeModuleClient,
40677
+ client: deps.client
40678
+ }) : undefined;
40679
+ const syncModuleDomain = async (domain, pageBudget) => {
40170
40680
  if (!rustModeModuleClient?.mirrorPull)
40171
40681
  return;
40172
40682
  await drainMirrorPages({
40173
40683
  db,
40174
40684
  module: rustModeModuleClient,
40175
40685
  domain,
40176
- limit: 1000
40686
+ limit: 1000,
40687
+ pageBudget
40177
40688
  });
40178
40689
  };
40179
40690
  const syncModuleNotes = () => syncModuleDomain("notes");
40180
- 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
+ };
40181
40714
  const rustToolBackends = deps.config.transform_mode === "rust" && rustModeModuleClient ? {
40182
40715
  authorityState: async ({ projectPath, projectRoot, sessionId, domain }) => {
40183
40716
  if (!rustModeModuleClient.authorityStatus)
@@ -40218,7 +40751,7 @@ function createMagicContextHook(deps) {
40218
40751
  filter,
40219
40752
  limit,
40220
40753
  offset,
40221
- noteId
40754
+ noteIds
40222
40755
  }) => {
40223
40756
  const response = await rustModeModuleClient.call({
40224
40757
  sessionId,
@@ -40239,13 +40772,13 @@ function createMagicContextHook(deps) {
40239
40772
  filter,
40240
40773
  limit,
40241
40774
  offset,
40242
- note_id: noteId
40775
+ note_ids: noteIds
40243
40776
  }
40244
40777
  }
40245
40778
  });
40246
40779
  await syncModuleNotes();
40247
40780
  if (compileStatus && !moduleNoteResponseIsError(response)) {
40248
- const moduleRowId = action === "write" ? moduleNoteRowId(response) : noteId ?? null;
40781
+ const moduleRowId = action === "write" ? moduleNoteRowId(response) : noteIds?.[0] ?? null;
40249
40782
  if (moduleRowId === null || !applyMirroredNoteCompileFields({
40250
40783
  db,
40251
40784
  moduleProject: memoryProject,
@@ -40274,6 +40807,11 @@ function createMagicContextHook(deps) {
40274
40807
  reason,
40275
40808
  limit
40276
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;
40277
40815
  const response = await rustModeModuleClient.call({
40278
40816
  sessionId,
40279
40817
  projectRoot,
@@ -40285,14 +40823,24 @@ function createMagicContextHook(deps) {
40285
40823
  action,
40286
40824
  content,
40287
40825
  category,
40288
- ids,
40826
+ ids: moduleIds,
40827
+ host_ids: hostIds,
40828
+ memory_id_lane: "host",
40289
40829
  reason,
40290
40830
  limit,
40291
40831
  memory_project: memoryProject
40292
40832
  }
40293
40833
  }
40294
40834
  });
40295
- 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
+ }
40296
40844
  if (!moduleNoteResponseIsError(response) && (action === "write" || action === "update" || action === "merge")) {
40297
40845
  (async () => {
40298
40846
  await ensureProjectRegisteredFromOpenCodeDirectory(projectRoot, db);
@@ -40304,7 +40852,13 @@ function createMagicContextHook(deps) {
40304
40852
  log("[magic-context] mirrored memory embedding failed:", error);
40305
40853
  });
40306
40854
  }
40307
- return response;
40855
+ return translateModuleMemoryMutationReply({
40856
+ db,
40857
+ moduleProject: memoryProject,
40858
+ response,
40859
+ requestedHostIds: hostIds,
40860
+ requestedCategory: category
40861
+ }) ?? response;
40308
40862
  },
40309
40863
  noteEvaluationAvailable: (evaluationProjectPath) => getModuleNoteEvaluationBridge(evaluationProjectPath) !== undefined,
40310
40864
  memorySync: (sessionId) => {
@@ -40441,7 +40995,8 @@ function createMagicContextHook(deps) {
40441
40995
  tsAuthorityRecoveryModuleClient: authorityRecoveryModuleClient,
40442
40996
  rustMemorySyncRequestedSessions,
40443
40997
  onRustModeParked: notifyRustModeParked,
40444
- onRustModeProjectPrepared: ensureModuleNoteEvaluationBridge
40998
+ onRustModeProjectPrepared: ensureModuleNoteEvaluationBridge,
40999
+ onRustEngineReconnectRefusal: (args) => rustRefusalRecovery?.arm(args)
40445
41000
  });
40446
41001
  const eventHandler = createEventHandler2({
40447
41002
  contextUsageMap,
@@ -40466,6 +41021,7 @@ function createMagicContextHook(deps) {
40466
41021
  },
40467
41022
  rustSessionCleanup: rustModeModuleClient !== undefined,
40468
41023
  onSessionDeleted: async (sessionId) => {
41024
+ rustRefusalRecovery?.forget(sessionId);
40469
41025
  dropSlot(sessionId, "session-deleted");
40470
41026
  try {
40471
41027
  await transform.clearRustSession(sessionId);
@@ -41108,6 +41664,46 @@ Two recovery modes for finer detail:
41108
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.`;
41109
41665
  var CTX_EXPAND_TOKEN_BUDGET = 15000;
41110
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
+
41111
41707
  // src/tools/ctx-expand/render.ts
41112
41708
  function isRecord5(value) {
41113
41709
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -41301,27 +41897,26 @@ function createCtxExpandTool(deps) {
41301
41897
  message: "number"
41302
41898
  });
41303
41899
  const sessionId = toolContext.sessionID;
41304
- if (args.message !== undefined) {
41305
- if (typeof args.message !== "number" || !Number.isInteger(args.message) || args.message < 1) {
41306
- return "Error: message must be a positive integer.";
41307
- }
41308
- return renderMessageByOrdinal(sessionId, args.message);
41900
+ const mode = resolveCtxExpandMode(args, "positive");
41901
+ if (mode.kind === "error") {
41902
+ return mode.message;
41309
41903
  }
41310
- if (!args.start || !args.end || !Number.isInteger(args.start) || !Number.isInteger(args.end) || args.start < 1 || args.end < args.start) {
41311
- 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);
41312
41906
  }
41907
+ const { start, end, verbose } = mode;
41313
41908
  const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, sessionId);
41314
- if (lastCompartmentEnd >= 0 && args.start > lastCompartmentEnd) {
41315
- 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.`;
41316
41911
  }
41317
- const effectiveEnd = lastCompartmentEnd >= 0 ? Math.min(args.end, lastCompartmentEnd) : args.end;
41318
- if (args.verbose === true) {
41319
- 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);
41320
41915
  if (!v.text) {
41321
- 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.`;
41322
41917
  }
41323
41918
  const out = [
41324
- `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>):`,
41325
41920
  "",
41326
41921
  v.text
41327
41922
  ];
@@ -41331,9 +41926,9 @@ function createCtxExpandTool(deps) {
41331
41926
  return out.join(`
41332
41927
  `);
41333
41928
  }
41334
- const chunk = readSessionChunk(sessionId, CTX_EXPAND_TOKEN_BUDGET, args.start, effectiveEnd + 1);
41929
+ const chunk = readSessionChunk(sessionId, CTX_EXPAND_TOKEN_BUDGET, start, effectiveEnd + 1);
41335
41930
  if (!chunk.text || chunk.messageCount === 0) {
41336
- 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.`;
41337
41932
  }
41338
41933
  const lines = [];
41339
41934
  lines.push(`Messages ${chunk.startIndex}-${chunk.endIndex} (${chunk.messageCount} messages, ~${chunk.tokenEstimate} tokens):`);
@@ -41396,7 +41991,7 @@ function isMemoryCategory(value) {
41396
41991
  return MEMORY_CATEGORIES.has(value);
41397
41992
  }
41398
41993
  function normalizeLimit2(limit) {
41399
- if (typeof limit !== "number" || !Number.isFinite(limit)) {
41994
+ if (typeof limit !== "number" || !Number.isFinite(limit) || limit === 0) {
41400
41995
  return DEFAULT_SEARCH_LIMIT;
41401
41996
  }
41402
41997
  return Math.max(1, Math.floor(limit));
@@ -41680,14 +42275,17 @@ function createCtxMemoryTool(deps) {
41680
42275
  await deps.ensureProjectRegistered?.(toolContext.directory, deps.db);
41681
42276
  const activeCurateCategory = toolContext.agent === DREAMER_AGENT ? getActiveCurateCategory(deps.db, projectPath) : null;
41682
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";
41683
42281
  const scopeRefusal = getCurateCategoryScopeRefusal({
41684
42282
  scope: activeCurateCategory,
41685
42283
  action: args.action,
41686
- requestedCategory: args.category,
41687
- ids: [
42284
+ requestedCategory: usesCategory ? args.category : undefined,
42285
+ ids: usesIds ? [
41688
42286
  ...args.ids ?? [],
41689
- ...Number.isInteger(args.superseded_by) ? [args.superseded_by] : []
41690
- ],
42287
+ ...usesSuccessor && Number.isInteger(args.superseded_by) ? [args.superseded_by] : []
42288
+ ] : [],
41691
42289
  categoryForId: (id) => {
41692
42290
  const category = getMemoryById(deps.db, id)?.category;
41693
42291
  return category ? curateCategoryForMemoryCategory(category) : null;
@@ -42088,7 +42686,7 @@ Use a note when something matters LATER but not in the next few steps: "revisit
42088
42686
  Actions:
42089
42687
  - write: save a note (content). Add surface_condition to make it a smart note (below).
42090
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.
42091
- - 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=[...]).
42092
42690
 
42093
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:
42094
42692
  ✓ "When PR #42 in cortexkit/magic-context is merged"
@@ -42101,7 +42699,7 @@ Example: ctx_note(action="write", content="Re-run the perf benchmark once the bo
42101
42699
  import { tool as tool3 } from "@opencode-ai/plugin";
42102
42700
 
42103
42701
  // src/features/magic-context/smart-notes/condition-compiler.ts
42104
- import { existsSync as existsSync12 } from "node:fs";
42702
+ import { existsSync as existsSync13 } from "node:fs";
42105
42703
  import { isAbsolute as isAbsolute4 } from "node:path";
42106
42704
 
42107
42705
  // ../retina-local-fs/src/path-fence.ts
@@ -42358,7 +42956,7 @@ async function compileSurfaceCondition(surfaceCondition, options) {
42358
42956
  dataDirectory: options.dataDirectory,
42359
42957
  cwd: options.projectPath
42360
42958
  });
42361
- return { path: resolved, exists: existsSync12(resolved) };
42959
+ return { path: resolved, exists: existsSync13(resolved) };
42362
42960
  });
42363
42961
  let predicates;
42364
42962
  try {
@@ -42613,7 +43211,7 @@ function formatNoteLine(note) {
42613
43211
  }
42614
43212
  var DISMISS_FOOTER = `
42615
43213
 
42616
- 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])`;
42617
43215
  var DEFAULT_READ_LIMIT = 25;
42618
43216
  function paginateNewestFirst(notes, limit, offset) {
42619
43217
  const total = notes.length;
@@ -42728,8 +43326,7 @@ var ctxNoteArgsShape = {
42728
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."),
42729
43327
  limit: tool3.schema.number().optional().describe("Max notes per section for read, newest first (default: 25)"),
42730
43328
  offset: tool3.schema.number().optional().describe("Skip this many newest notes for read — page older ones (default: 0)"),
42731
- note_id: tool3.schema.number().optional().describe("Note ID (required for 'dismiss' and 'update' actions)."),
42732
- 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'.")
42733
43330
  };
42734
43331
  var ctxNoteArgsSchema = tool3.schema.object(ctxNoteArgsShape).passthrough();
42735
43332
  function formatDismissResults(results) {
@@ -42738,9 +43335,10 @@ function formatDismissResults(results) {
42738
43335
  ${results.map((result) => `- Note #${result.noteId}: ${result.outcome}`).join(`
42739
43336
  `)}`;
42740
43337
  }
42741
- function parseDismissNoteIds(value) {
42742
- if (!Array.isArray(value) || value.length < 1 || value.length > 50 || value.some((id) => typeof id !== "number" || !Number.isInteger(id) || id <= 0)) {
42743
- 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'.";
42744
43342
  }
42745
43343
  return value;
42746
43344
  }
@@ -42761,22 +43359,13 @@ function createCtxNoteTool(deps) {
42761
43359
  },
42762
43360
  limit: "number",
42763
43361
  offset: "number",
42764
- note_id: "number",
42765
43362
  note_ids: { type: "array", items: "number", maxItems: 50 }
42766
43363
  });
42767
43364
  const sessionId = toolContext.sessionID;
42768
43365
  const action = args.action ?? (args.content?.trim() ? "write" : "read");
42769
- const hasNoteId = args.note_id !== undefined;
42770
- const hasNoteIds = args.note_ids !== undefined;
42771
- if (hasNoteId && hasNoteIds) {
42772
- return "Error: 'note_id' and 'note_ids' cannot be used together; provide one or the other.";
42773
- }
42774
- if (hasNoteIds && action !== "dismiss") {
42775
- return "Error: 'note_ids' is only valid when action is 'dismiss'.";
42776
- }
42777
- const dismissNoteIds = action === "dismiss" && hasNoteIds ? parseDismissNoteIds(args.note_ids) : undefined;
42778
- if (typeof dismissNoteIds === "string")
42779
- return dismissNoteIds;
43366
+ const noteIds = action === "dismiss" || action === "update" ? parseNoteIds(action, args.note_ids) : undefined;
43367
+ if (typeof noteIds === "string")
43368
+ return noteIds;
42780
43369
  const wakePlaneActive = action === "write" && Boolean(args.surface_condition?.trim()) && await wakePlaneStatus() === "present";
42781
43370
  const surfaceCondition = wakePlaneActive ? undefined : args.surface_condition?.trim();
42782
43371
  const projectIdentity = deps.resolveProjectPath?.(toolContext.directory);
@@ -42825,8 +43414,7 @@ function createCtxNoteTool(deps) {
42825
43414
  filter: args.filter,
42826
43415
  limit: args.limit,
42827
43416
  offset: args.offset,
42828
- noteId: args.note_id,
42829
- noteIds: dismissNoteIds
43417
+ noteIds: Array.isArray(noteIds) ? noteIds : undefined
42830
43418
  };
42831
43419
  try {
42832
43420
  const text = moduleNoteText(await rustNote(request), args, action);
@@ -42895,33 +43483,24 @@ wake plane active — create a scheduled wake instead; stored as a plain note.`;
42895
43483
  return `Saved session note #${note.id}.`;
42896
43484
  }
42897
43485
  if (action === "dismiss") {
42898
- if (dismissNoteIds) {
42899
- if (!projectIdentity) {
42900
- return "Error: Could not resolve project identity for note dismiss.";
42901
- }
42902
- return formatDismissResults(dismissNotes(deps.db, dismissNoteIds, {
42903
- projectPath: projectIdentity,
42904
- sessionId
42905
- }));
42906
- }
42907
- const noteId = args.note_id;
42908
- if (typeof noteId !== "number") {
42909
- return "Error: 'note_id' is required when action is 'dismiss'.";
42910
- }
42911
43486
  if (!projectIdentity) {
42912
43487
  return "Error: Could not resolve project identity for note dismiss.";
42913
43488
  }
42914
- 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, {
42915
43498
  projectPath: projectIdentity,
42916
43499
  sessionId
42917
- });
42918
- return dismissed ? `Note #${noteId} dismissed.` : `Error: Note #${noteId} not found in your session/project or already dismissed.`;
43500
+ }));
42919
43501
  }
42920
43502
  if (action === "update") {
42921
- const noteId = args.note_id;
42922
- if (typeof noteId !== "number") {
42923
- return "Error: 'note_id' is required when action is 'update'.";
42924
- }
43503
+ const noteId = noteIds[0];
42925
43504
  const updates = {};
42926
43505
  if (args.content?.trim())
42927
43506
  updates.content = args.content.trim();
@@ -42993,7 +43572,7 @@ function createCtxNoteTools(deps) {
42993
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".
42994
43573
 
42995
43574
  How it works:
42996
- - 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.
42997
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.
42998
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?".
42999
43578
 
@@ -43238,7 +43817,7 @@ var VALID_SOURCES = new Set([
43238
43817
  "note"
43239
43818
  ]);
43240
43819
  function normalizeLimit3(limit) {
43241
- if (typeof limit !== "number" || !Number.isFinite(limit)) {
43820
+ if (typeof limit !== "number" || !Number.isFinite(limit) || limit === 0) {
43242
43821
  return DEFAULT_CTX_SEARCH_LIMIT;
43243
43822
  }
43244
43823
  return Math.max(1, Math.floor(limit));
@@ -43257,7 +43836,7 @@ function normalizeSources(sources) {
43257
43836
  }
43258
43837
  }
43259
43838
  }
43260
- return result;
43839
+ return sources.length === 0 ? undefined : result;
43261
43840
  }
43262
43841
  var ctxSearchArgsShape = {
43263
43842
  query: tool5.schema.string().optional().describe("Search query. Matches against memory content, Primers, git commit messages, and raw user/assistant message text."),
@@ -43484,7 +44063,7 @@ import {
43484
44063
  chmodSync as chmodSync2,
43485
44064
  mkdirSync as mkdirSync4,
43486
44065
  readdirSync,
43487
- readFileSync as readFileSync6,
44066
+ readFileSync as readFileSync7,
43488
44067
  renameSync as renameSync5,
43489
44068
  rmSync as rmSync4,
43490
44069
  unlinkSync as unlinkSync2,
@@ -43621,7 +44200,7 @@ class MagicContextRpcServer {
43621
44200
  for (const entry of readdirSync(this.portDir)) {
43622
44201
  if (!entry.startsWith("port-") || !entry.endsWith(".json"))
43623
44202
  continue;
43624
- const record = parseRpcPortFile(readFileSync6(`${this.portDir}/${entry}`, "utf-8"));
44203
+ const record = parseRpcPortFile(readFileSync7(`${this.portDir}/${entry}`, "utf-8"));
43625
44204
  if (!record || record.pid === process.pid || !isPidAlive(record.pid))
43626
44205
  continue;
43627
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`);
@@ -44015,7 +44594,240 @@ ${data.output?.output ?? ""}`
44015
44594
  return [...content.length ? [make("assistant", content)] : [], ...results];
44016
44595
  }
44017
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
+
44018
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
+ }
44019
44831
  function meter(system, prompt, text) {
44020
44832
  return {
44021
44833
  input: estimateTokens(system) + estimateTokens(prompt),
@@ -44024,115 +44836,294 @@ function meter(system, prompt, text) {
44024
44836
  cacheWrite: 0
44025
44837
  };
44026
44838
  }
44027
- 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) {
44028
44914
  const runs = new WeakMap;
44029
- const pending = new Map;
44030
- const modelKey = (model) => `${model.providerID}/${model.modelID}`;
44031
- const requireModel = async (sessionID) => {
44032
- const model = sessionID ? await currentModel(sessionID) : null;
44033
- if (!model)
44034
- throw new HiddenCompletionRefusal("hidden_model_unsupported", "Hidden completion requires an existing session with a resolved current model", true);
44035
- return model;
44036
- };
44037
- const refuseModel = (requested, actual, terminal = false) => {
44038
- 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);
44039
- };
44040
- await host.hook("generate", async (draft) => {
44041
- const last = draft.messages.at(-1);
44042
- if (last?.role !== "user" || last.content.length !== 1)
44043
- return;
44044
- const part = last.content[0];
44045
- if (part?.type !== "text" || typeof part.text !== "string")
44046
- return;
44047
- const attempt = pending.get(part.text);
44048
- if (!attempt || draft.sessionID !== attempt.run.identity.parentSessionId)
44049
- return;
44050
- const actual = `${draft.model.providerID}/${draft.model.id}`;
44051
- if (modelKey(attempt.model) !== actual)
44052
- refuseModel(modelKey(attempt.model), actual);
44053
- if (attempt.request.signal?.aborted)
44054
- throw new Error("Hidden completion prompt aborted");
44055
- const body = attempt.request.body;
44056
- if (typeof body.variant === "string" && body.variant !== draft.model.variant) {
44057
- 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);
44058
44944
  }
44059
- const parts = body.parts;
44060
- if (!Array.isArray(parts) || parts.some((item) => item.type !== "text" || typeof item.text !== "string")) {
44061
- 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);
44062
44948
  }
44063
- draft.system = [
44064
- {
44065
- type: "text",
44066
- 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 } : {}
44067
44964
  }
44068
- ];
44069
- draft.messages = [
44070
- { role: "user", content: parts.map((item) => ({ type: "text", text: item.text })) }
44071
- ];
44072
- draft.tools = {};
44073
- if (attempt.run.identity.maxOutputTokens !== undefined)
44074
- draft.options.maxOutputTokens = attempt.run.identity.maxOutputTokens;
44075
- attempt.shaped = true;
44076
- });
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
+ };
44077
44981
  return {
44078
44982
  capabilities: { tools: false, harness: "opencode2" },
44079
44983
  async open(identity) {
44080
- const model = await requireModel(identity.parentSessionId);
44081
- const configured = (identity.configuredModels ?? (identity.model ? [identity.model] : [])).map(toModelEntry).filter((entry) => entry !== undefined);
44082
- if (configured.length && !configured.some((entry) => entry.model === modelKey(model))) {
44083
- refuseModel(configured.map((entry) => entry.model).join(", "), modelKey(model), true);
44084
- }
44085
- const handle = { id: `mc:hidden:${crypto.randomUUID()}` };
44086
- runs.set(handle, { identity });
44087
- 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
+ }
44088
45049
  },
44089
45050
  async attempt(handle, request) {
44090
45051
  const run = runs.get(handle);
44091
45052
  if (!run)
44092
45053
  throw new Error("Unknown hidden completion run");
44093
- const sessionID = run.identity.parentSessionId;
44094
- const actual = await requireModel(sessionID);
44095
- if (sessionID === undefined)
44096
- throw new Error("Hidden completion session id missing");
44097
- const requested = request.body.model ?? actual;
44098
- if (modelKey(requested) !== modelKey(actual))
44099
- refuseModel(modelKey(requested), modelKey(actual));
44100
- if (request.signal?.aborted)
45054
+ if (request.signal?.aborted) {
45055
+ await interruptAndRetire(run, "aborted-before-prompt");
44101
45056
  throw new Error("Hidden completion prompt aborted");
44102
- const marker = `${handle.id}:${crypto.randomUUID()}`;
44103
- const state = { run, request, model: actual, shaped: false };
44104
- 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 });
44105
45081
  try {
44106
- const generation = host.generate({ sessionID, prompt: marker }, { signal: request.signal });
44107
- const result = await new Promise((resolve, reject) => {
44108
- const abort = () => reject(new Error("Hidden completion prompt aborted"));
44109
- request.signal?.addEventListener("abort", abort, { once: true });
44110
- generation.then(resolve, reject).finally(() => {
44111
- request.signal?.removeEventListener("abort", abort);
44112
- pending.delete(marker);
44113
- });
44114
- if (request.signal?.aborted)
44115
- abort();
44116
- });
44117
- if (!state.shaped)
44118
- throw new HiddenCompletionRefusal("hidden_prompt_unrecognized", "Host did not dispatch the hidden generate hook", true);
44119
- const prompt = request.body.parts.map((part) => part.text).join(`
44120
- `);
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);
44121
45100
  const system = typeof request.body.system === "string" ? request.body.system : run.identity.system;
44122
- const usage = meter(system, prompt, result.text);
44123
- usage.input += run.usage?.input ?? 0;
44124
- usage.output += run.usage?.output ?? 0;
44125
- run.usage = usage;
45101
+ const tokens = row.data.tokens;
44126
45102
  run.completion = {
44127
- text: result.text,
44128
- usage,
44129
- lengthCapped: false,
44130
- providerId: actual.providerID,
44131
- 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
44132
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;
44133
45123
  } finally {
44134
- if (!request.signal?.aborted)
44135
- pending.delete(marker);
45124
+ clearTimeout(deadlineTimer);
45125
+ request.signal?.removeEventListener("abort", onAbort);
45126
+ options.hook.releaseAttempt(marker);
44136
45127
  }
44137
45128
  },
44138
45129
  async collect(handle) {
@@ -44141,18 +45132,27 @@ async function createV2HiddenCompletionExecutor(host, currentModel) {
44141
45132
  throw new Error("Hidden completion has no settled output");
44142
45133
  return completion;
44143
45134
  },
44144
- 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
+ }
44145
45150
  };
44146
45151
  }
44147
45152
 
44148
45153
  // src/v2/store-reader.ts
44149
- import { join as join11 } from "node:path";
44150
- function sourceDatabaseFilename(channel, env = process.env) {
44151
- 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`);
44152
- }
44153
45154
  function gaDatabasePath(dataHome, channel = "latest", env = process.env) {
44154
- const filename = sourceDatabaseFilename(channel, env);
44155
- return filename === ":memory:" ? filename : join11(dataHome, "opencode", filename);
45155
+ return resolveOpenCodeDbPath("v2", { dataHome, channel, env }).path;
44156
45156
  }
44157
45157
  function decode(row) {
44158
45158
  const data = JSON.parse(row.data);
@@ -44169,6 +45169,12 @@ class V2StoreReader {
44169
45169
  db;
44170
45170
  constructor(path) {
44171
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
+ }
44172
45178
  }
44173
45179
  close() {
44174
45180
  this.db.close();
@@ -44194,6 +45200,16 @@ class V2StoreReader {
44194
45200
  idleRows(sessionID, after = -1) {
44195
45201
  return this.all(sessionID, after, "idle");
44196
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
+ }
44197
45213
  window(sessionID) {
44198
45214
  return this.db.transaction(() => {
44199
45215
  const cut = this.latestCompaction(sessionID);
@@ -44443,7 +45459,7 @@ function rawMessages(rows, history = rows) {
44443
45459
  const ordinals = new Map(history.filter(isRawRow).map((row, index) => [row.id, index + 1]));
44444
45460
  return rows.filter(isRawRow).map((row) => ({
44445
45461
  id: row.id,
44446
- ordinal: ordinals.get(row.id),
45462
+ ordinal: ordinals.get(row.id) ?? 0,
44447
45463
  role: row.type === "assistant" ? "assistant" : "user",
44448
45464
  createdAt: row.data.time?.created,
44449
45465
  parts: row.type === "assistant" ? (row.data.content ?? []).map((part) => {
@@ -44477,20 +45493,96 @@ function createHostSeams(context, read, liveModels) {
44477
45493
  hostRefuse: (_client, sessionID) => interruptBeforeProvider(context.session, sessionID)
44478
45494
  };
44479
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
+ }
44480
45550
  async function registerContext(context) {
44481
45551
  const directory = context.location.directory;
44482
45552
  const config = loadPluginConfigDetailed(directory).config;
44483
45553
  if (!config.enabled || !isCompactionEnabled(config))
44484
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
+ }
44485
45563
  const folds = new FoldOwner(context.storage);
44486
45564
  const limits = new Map;
44487
45565
  const queriedModels = new Set;
44488
45566
  const liveModels = new Map;
44489
- const hostGenerate = context.session.generate?.bind(context.session);
44490
- const hiddenCompletionExecutor = hostGenerate ? await createV2HiddenCompletionExecutor({
44491
- hook: (name, callback) => context.session.hook(name, callback),
44492
- generate: (input, options) => hostGenerate(input, options)
44493
- }, (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;
44494
45586
  const dreamTrigger = hiddenCompletionExecutor && config.dreamer && !config.dreamer.disable ? startDreamTrigger(context, {
44495
45587
  config: config.dreamer,
44496
45588
  executor: hiddenCompletionExecutor,
@@ -44511,16 +45603,17 @@ async function registerContext(context) {
44511
45603
  let toolDuties;
44512
45604
  await context.tool.hook("execute.before", (draft) => assertExecutableToolInput(draft.input));
44513
45605
  await context.tool.hook("execute.after", async (draft) => {
44514
- if (!db || draft.status !== "completed")
45606
+ if (!db)
45607
+ return;
45608
+ if (draft.status && draft.status !== "completed")
44515
45609
  return;
44516
45610
  try {
44517
45611
  toolDuties ??= createToolExecuteAfterHook({ db, channel1StateBySession: channel1 });
44518
- const content = draft.result?.content;
44519
- const text = typeof content === "string" ? content : Array.isArray(content) ? content.filter((part) => part.type === "text").map((part) => part.text).join(`
44520
- `) : "";
45612
+ const text = toolResultText(draft.result);
44521
45613
  const output = { output: text };
44522
45614
  await toolDuties({ ...draft, args: draft.input }, output);
44523
45615
  if (draft.result && output.output !== text) {
45616
+ const content = draft.result.content;
44524
45617
  if (typeof content === "string")
44525
45618
  draft.result.content = output.output;
44526
45619
  else if (Array.isArray(content) && output.output.startsWith(text))
@@ -44545,7 +45638,6 @@ async function registerContext(context) {
44545
45638
  getCount: (sessionID) => read(sessionID).length
44546
45639
  });
44547
45640
  let transform;
44548
- let db;
44549
45641
  const refuseIfUnsafe = async (draft) => {
44550
45642
  let unsafe = false;
44551
45643
  try {
@@ -44559,10 +45651,8 @@ async function registerContext(context) {
44559
45651
  const tokens = latest?.data.tokens;
44560
45652
  const modelKey = `${draft.model.providerID}/${draft.model.id}`;
44561
45653
  if (!queriedModels.has(modelKey)) {
44562
- const catalog = await context.catalog.model.list({
44563
- location: context.location
44564
- });
44565
- for (const model of catalog.data)
45654
+ const catalog = await Promise.resolve(context.model.list());
45655
+ for (const model of catalogModels(catalog))
44566
45656
  limits.set(`${model.providerID}/${model.id}`, model.limit.context);
44567
45657
  queriedModels.add(modelKey);
44568
45658
  }
@@ -44582,7 +45672,8 @@ async function registerContext(context) {
44582
45672
  } finally {
44583
45673
  reader.close();
44584
45674
  }
44585
- } catch {
45675
+ } catch (error) {
45676
+ console.warn("[magic-context] v2 refuseIfUnsafe", error);
44586
45677
  unsafe = true;
44587
45678
  }
44588
45679
  if (unsafe)
@@ -44635,6 +45726,26 @@ async function registerContext(context) {
44635
45726
  }
44636
45727
  });
44637
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
+ }
44638
45749
  let postFold = false;
44639
45750
  try {
44640
45751
  if (await refuseIfUnsafe(draft))
@@ -44751,6 +45862,9 @@ async function registerContext(context) {
44751
45862
  const mapped = adaptPayload(draft, admitted);
44752
45863
  await transform({}, mapped);
44753
45864
  mapped.commit();
45865
+ if (db) {
45866
+ await deliverPendingChannel2(context, db, draft.sessionID, channel1.get(draft.sessionID));
45867
+ }
44754
45868
  if (checkpoint && submitted !== undefined) {
44755
45869
  const head = draft.messages.find((message) => message.id === HEAD_IDS[0]);
44756
45870
  const baseline = head?.content.find((part) => part.type === "text")?.text;
@@ -44790,18 +45904,18 @@ ${baseline}
44790
45904
  }
44791
45905
 
44792
45906
  // src/v2/hooks/update-check.ts
44793
- import { existsSync as existsSync13, readFileSync as readFileSync7 } from "node:fs";
44794
- 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";
44795
45909
  import { fileURLToPath as fileURLToPath3 } from "node:url";
44796
45910
  function packageInfo() {
44797
45911
  let directory = dirname8(fileURLToPath3(import.meta.url));
44798
45912
  for (;; ) {
44799
45913
  try {
44800
- const pkg = JSON.parse(readFileSync7(join12(directory, "package.json"), "utf8"));
45914
+ const pkg = JSON.parse(readFileSync8(join11(directory, "package.json"), "utf8"));
44801
45915
  if (pkg.name === "@cortexkit/opencode-magic-context" && typeof pkg.version === "string") {
44802
45916
  return {
44803
45917
  version: pkg.version,
44804
- development: existsSync13(join12(directory, "src/v2/server.ts"))
45918
+ development: existsSync14(join11(directory, "src/v2/server.ts"))
44805
45919
  };
44806
45920
  }
44807
45921
  } catch {}
@@ -44848,7 +45962,17 @@ function startUpdateChecks(context, check = (signal) => {
44848
45962
  }
44849
45963
 
44850
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
+ }
44851
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
+ }
44852
45976
  setHarness("opencode2");
44853
45977
  const duties = await registerContext(context);
44854
45978
  const checks = loadPluginConfigDetailed(context.location.directory).config.auto_update === false ? undefined : startUpdateChecks(context);
@@ -44872,6 +45996,7 @@ var server = async (ctx) => {
44872
45996
  let hooksDeadlineReason = null;
44873
45997
  let deadlinePhase = null;
44874
45998
  emitBootEnteringBreadcrumb(process.pid, ctx.directory, log, flushLogger);
45999
+ setHarness("opencode");
44875
46000
  const configStartedAt = performance.now();
44876
46001
  beginBootQuietPeriod();
44877
46002
  const configMigrationWarnings = migrateMagicContextConfigLocations(ctx.directory, {
@@ -44911,7 +46036,7 @@ var server = async (ctx) => {
44911
46036
  if (hasBannerEntries)
44912
46037
  setTimeout(async () => {
44913
46038
  try {
44914
- const { sendStatusNotification } = await import("./send-session-notification-xnypkxpx.js");
46039
+ const { sendStatusNotification } = await import("./send-session-notification-jbxknvar.js");
44915
46040
  const clientWithSessions = ctx.client;
44916
46041
  const sessions = await Promise.resolve(clientWithSessions.session?.list?.()).catch(() => null);
44917
46042
  const sessionList = Array.isArray(sessions) ? sessions : sessions?.data;
@@ -44928,7 +46053,7 @@ var server = async (ctx) => {
44928
46053
  log(`[magic-context] opencode_db_missing path=${openCodeDbResolution.path} source=${openCodeDbResolution.source}`);
44929
46054
  setTimeout(async () => {
44930
46055
  try {
44931
- const { sendStatusNotification } = await import("./send-session-notification-xnypkxpx.js");
46056
+ const { sendStatusNotification } = await import("./send-session-notification-jbxknvar.js");
44932
46057
  const clientWithSessions = ctx.client;
44933
46058
  const sessions = await Promise.resolve(clientWithSessions.session?.list?.()).catch(() => null);
44934
46059
  const sessionList = Array.isArray(sessions) ? sessions : sessions?.data;
@@ -45174,12 +46299,12 @@ var server = async (ctx) => {
45174
46299
  {
45175
46300
  const fence = getSchemaFenceRejection();
45176
46301
  if (fence) {
45177
- 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));
45178
46303
  }
45179
46304
  }
45180
46305
  const serverUrl = ctx.serverUrl;
45181
46306
  const serverUrlStr = serverUrl instanceof URL ? serverUrl.toString().replace(/\/$/, "") : typeof serverUrl === "string" ? serverUrl.replace(/\/$/, "") : undefined;
45182
- import("./send-session-notification-xnypkxpx.js").then(({ setNotificationServerUrl }) => {
46307
+ import("./send-session-notification-jbxknvar.js").then(({ setNotificationServerUrl }) => {
45183
46308
  setNotificationServerUrl(serverUrlStr);
45184
46309
  });
45185
46310
  if (conflictResult?.hasConflict) {
@@ -45202,7 +46327,7 @@ var server = async (ctx) => {
45202
46327
  }) => {
45203
46328
  if (!shouldShowAnnouncement())
45204
46329
  return;
45205
- 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));
45206
46331
  }).catch(() => {});
45207
46332
  }, 8000);
45208
46333
  }