@cortexkit/opencode-magic-context 0.17.2 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +1 -1
  2. package/dist/config/index.d.ts.map +1 -1
  3. package/dist/features/magic-context/compaction-marker.d.ts +17 -0
  4. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  5. package/dist/features/magic-context/compartment-storage.d.ts +11 -0
  6. package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
  7. package/dist/features/magic-context/dreamer/queue.d.ts.map +1 -1
  8. package/dist/features/magic-context/dreamer/runner.d.ts +15 -0
  9. package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
  10. package/dist/features/magic-context/memory/embedding-identity.d.ts +11 -0
  11. package/dist/features/magic-context/memory/embedding-identity.d.ts.map +1 -0
  12. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  13. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  14. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  15. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  16. package/dist/features/magic-context/sidekick/agent.d.ts.map +1 -1
  17. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  18. package/dist/features/magic-context/storage-meta-persisted.d.ts +70 -0
  19. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  20. package/dist/features/magic-context/storage-meta-shared.d.ts +1 -0
  21. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  22. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  23. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  24. package/dist/features/magic-context/storage.d.ts +1 -1
  25. package/dist/features/magic-context/storage.d.ts.map +1 -1
  26. package/dist/features/magic-context/tool-definition-tokens.d.ts.map +1 -1
  27. package/dist/features/magic-context/types.d.ts +1 -0
  28. package/dist/features/magic-context/types.d.ts.map +1 -1
  29. package/dist/features/magic-context/user-memory/review-user-memories.d.ts +2 -0
  30. package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
  31. package/dist/hooks/magic-context/cache-busting-signals.d.ts +10 -0
  32. package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -0
  33. package/dist/hooks/magic-context/command-handler.d.ts +2 -0
  34. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  35. package/dist/hooks/magic-context/compaction-marker-manager.d.ts +50 -0
  36. package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/compartment-runner-compressor.d.ts +1 -0
  38. package/dist/hooks/magic-context/compartment-runner-compressor.d.ts.map +1 -1
  39. package/dist/hooks/magic-context/compartment-runner-historian.d.ts +7 -0
  40. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  41. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts +1 -1
  42. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  43. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/compartment-runner-types.d.ts +18 -7
  46. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  47. package/dist/hooks/magic-context/compartment-runner.d.ts +7 -2
  48. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  49. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/historian-state-file.d.ts +25 -11
  51. package/dist/hooks/magic-context/historian-state-file.d.ts.map +1 -1
  52. package/dist/hooks/magic-context/hook-handlers.d.ts +11 -4
  53. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  54. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/inject-compartments.d.ts +2 -1
  56. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/live-session-state.d.ts +3 -1
  58. package/dist/hooks/magic-context/live-session-state.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  60. package/dist/hooks/magic-context/todo-view.d.ts +102 -0
  61. package/dist/hooks/magic-context/todo-view.d.ts.map +1 -0
  62. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +11 -4
  63. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  64. package/dist/hooks/magic-context/transform-message-helpers.d.ts +22 -0
  65. package/dist/hooks/magic-context/transform-message-helpers.d.ts.map +1 -1
  66. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +15 -1
  67. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  68. package/dist/hooks/magic-context/transform.d.ts +4 -0
  69. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  70. package/dist/index.js +1789 -711
  71. package/dist/plugin/dream-timer.d.ts.map +1 -1
  72. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  73. package/dist/plugin/rpc-handlers.d.ts +2 -1
  74. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  75. package/dist/plugin/sidebar-snapshot-cache.d.ts.map +1 -1
  76. package/dist/shared/conflict-detector.d.ts +49 -0
  77. package/dist/shared/conflict-detector.d.ts.map +1 -1
  78. package/dist/shared/conflict-fixer.d.ts +1 -1
  79. package/dist/shared/conflict-fixer.d.ts.map +1 -1
  80. package/dist/shared/data-path.d.ts +84 -0
  81. package/dist/shared/data-path.d.ts.map +1 -1
  82. package/dist/shared/index.d.ts +1 -0
  83. package/dist/shared/index.d.ts.map +1 -1
  84. package/dist/shared/logger.d.ts +6 -0
  85. package/dist/shared/logger.d.ts.map +1 -1
  86. package/dist/shared/model-suggestion-retry.d.ts +37 -0
  87. package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
  88. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  89. package/dist/shared/resolve-fallbacks.d.ts +32 -0
  90. package/dist/shared/resolve-fallbacks.d.ts.map +1 -0
  91. package/dist/shared/rpc-client.d.ts +2 -1
  92. package/dist/shared/rpc-client.d.ts.map +1 -1
  93. package/dist/shared/rpc-notifications.d.ts +3 -2
  94. package/dist/shared/rpc-notifications.d.ts.map +1 -1
  95. package/dist/shared/rpc-server.d.ts +3 -0
  96. package/dist/shared/rpc-server.d.ts.map +1 -1
  97. package/dist/shared/rpc-types.d.ts +1 -0
  98. package/dist/shared/rpc-types.d.ts.map +1 -1
  99. package/dist/shared/rpc-utils.d.ts +13 -2
  100. package/dist/shared/rpc-utils.d.ts.map +1 -1
  101. package/dist/shared/stable-json.d.ts +21 -0
  102. package/dist/shared/stable-json.d.ts.map +1 -0
  103. package/dist/shared/tag-transcript.d.ts.map +1 -1
  104. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  105. package/dist/tui/data/context-db.d.ts.map +1 -1
  106. package/package.json +1 -1
  107. package/src/shared/conflict-detector.ts +4 -4
  108. package/src/shared/conflict-fixer.test.ts +124 -0
  109. package/src/shared/conflict-fixer.ts +34 -28
  110. package/src/shared/data-path.test.ts +38 -0
  111. package/src/shared/data-path.ts +99 -0
  112. package/src/shared/index.ts +1 -0
  113. package/src/shared/logger.ts +29 -3
  114. package/src/shared/model-suggestion-retry.test.ts +251 -0
  115. package/src/shared/model-suggestion-retry.ts +194 -6
  116. package/src/shared/models-dev-cache.ts +7 -7
  117. package/src/shared/resolve-fallbacks.test.ts +136 -0
  118. package/src/shared/resolve-fallbacks.ts +76 -0
  119. package/src/shared/rpc-client.test.ts +161 -0
  120. package/src/shared/rpc-client.ts +82 -22
  121. package/src/shared/rpc-notifications.test.ts +20 -0
  122. package/src/shared/rpc-notifications.ts +9 -6
  123. package/src/shared/rpc-server.ts +42 -4
  124. package/src/shared/rpc-types.ts +1 -0
  125. package/src/shared/rpc-utils.ts +59 -3
  126. package/src/shared/stable-json.test.ts +87 -0
  127. package/src/shared/stable-json.ts +37 -0
  128. package/src/shared/tag-transcript.ts +3 -2
  129. package/src/tui/data/context-db.ts +20 -1
  130. package/src/tui/index.tsx +114 -18
package/dist/index.js CHANGED
@@ -11403,7 +11403,7 @@ function finalize(ctx, schema) {
11403
11403
  result.$schema = "http://json-schema.org/draft-07/schema#";
11404
11404
  } else if (ctx.target === "draft-04") {
11405
11405
  result.$schema = "http://json-schema.org/draft-04/schema#";
11406
- } else if (ctx.target === "openapi-3.0") {} else {}
11406
+ } else if (ctx.target === "openapi-3.0") {}
11407
11407
  if (ctx.external?.uri) {
11408
11408
  const id = ctx.external.registry.get(schema)?.id;
11409
11409
  if (!id)
@@ -11664,7 +11664,7 @@ var formatMap, stringProcessor = (schema, ctx, _json, _params) => {
11664
11664
  if (val === undefined) {
11665
11665
  if (ctx.unrepresentable === "throw") {
11666
11666
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
11667
- } else {}
11667
+ }
11668
11668
  } else if (typeof val === "bigint") {
11669
11669
  if (ctx.unrepresentable === "throw") {
11670
11670
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -15017,10 +15017,56 @@ var init_magic_context = __esm(() => {
15017
15017
  });
15018
15018
  });
15019
15019
 
15020
- // src/shared/logger.ts
15021
- import * as fs from "node:fs";
15020
+ // src/shared/harness.ts
15021
+ function getHarness() {
15022
+ return currentHarness;
15023
+ }
15024
+ var currentHarness = "opencode";
15025
+
15026
+ // src/shared/data-path.ts
15022
15027
  import * as os from "node:os";
15023
15028
  import * as path from "node:path";
15029
+ function getDataDir() {
15030
+ return process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
15031
+ }
15032
+ function getMagicContextTempDir(harness = getHarness()) {
15033
+ return path.join(os.tmpdir(), harness, "magic-context");
15034
+ }
15035
+ function getMagicContextLogPath(harness = getHarness()) {
15036
+ return path.join(getMagicContextTempDir(harness), "magic-context.log");
15037
+ }
15038
+ function getProjectMagicContextDir(directory) {
15039
+ return path.join(directory, ".opencode", "magic-context");
15040
+ }
15041
+ function getProjectMagicContextHistorianDir(directory) {
15042
+ return path.join(getProjectMagicContextDir(directory), "historian");
15043
+ }
15044
+ function getOpenCodeStorageDir() {
15045
+ return path.join(getDataDir(), "opencode", "storage");
15046
+ }
15047
+ function getMagicContextStorageDir() {
15048
+ return path.join(getDataDir(), "cortexkit", "magic-context");
15049
+ }
15050
+ function getLegacyOpenCodeMagicContextStorageDir() {
15051
+ return path.join(getOpenCodeStorageDir(), "plugin", "magic-context");
15052
+ }
15053
+ function getCacheDir() {
15054
+ return process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), ".cache");
15055
+ }
15056
+ var init_data_path = () => {};
15057
+
15058
+ // src/shared/logger.ts
15059
+ import * as fs from "node:fs";
15060
+ import * as path2 from "node:path";
15061
+ function ensureDir(filePath) {
15062
+ const dir = path2.dirname(filePath);
15063
+ if (dir === lastEnsuredDir)
15064
+ return;
15065
+ try {
15066
+ fs.mkdirSync(dir, { recursive: true });
15067
+ lastEnsuredDir = dir;
15068
+ } catch {}
15069
+ }
15024
15070
  function flush() {
15025
15071
  if (flushTimer) {
15026
15072
  clearTimeout(flushTimer);
@@ -15031,6 +15077,8 @@ function flush() {
15031
15077
  const data = buffer.join("");
15032
15078
  buffer = [];
15033
15079
  try {
15080
+ const logFile = getMagicContextLogPath();
15081
+ ensureDir(logFile);
15034
15082
  fs.appendFileSync(logFile, data);
15035
15083
  } catch {}
15036
15084
  }
@@ -15061,9 +15109,9 @@ ${data.stack}` : ""}` : ` ${JSON.stringify(data)}`;
15061
15109
  function sessionLog(sessionId, message, data) {
15062
15110
  log(`[magic-context][${sessionId}] ${message}`, data);
15063
15111
  }
15064
- var logFile, isTestEnv = false, buffer, flushTimer = null, FLUSH_INTERVAL_MS = 500, BUFFER_SIZE_LIMIT = 50;
15112
+ var isTestEnv = false, buffer, flushTimer = null, FLUSH_INTERVAL_MS = 500, BUFFER_SIZE_LIMIT = 50, lastEnsuredDir = null;
15065
15113
  var init_logger = __esm(() => {
15066
- logFile = path.join(os.tmpdir(), "magic-context.log");
15114
+ init_data_path();
15067
15115
  buffer = [];
15068
15116
  if (!isTestEnv) {
15069
15117
  process.on("exit", flush);
@@ -15122,6 +15170,161 @@ var init_model_requirements = __esm(() => {
15122
15170
  };
15123
15171
  });
15124
15172
 
15173
+ // src/features/magic-context/overflow-detection.ts
15174
+ function extractErrorMessage(error51) {
15175
+ if (!error51)
15176
+ return "";
15177
+ if (typeof error51 === "string")
15178
+ return error51;
15179
+ if (typeof error51 === "object") {
15180
+ const obj = error51;
15181
+ const nested = obj.error;
15182
+ if (nested && typeof nested.message === "string" && nested.message.length > 0) {
15183
+ return nested.message;
15184
+ }
15185
+ }
15186
+ if (error51 instanceof Error)
15187
+ return error51.message;
15188
+ if (typeof error51 === "object") {
15189
+ const obj = error51;
15190
+ if (typeof obj.message === "string")
15191
+ return obj.message;
15192
+ if (typeof obj.responseBody === "string")
15193
+ return obj.responseBody;
15194
+ try {
15195
+ return JSON.stringify(error51);
15196
+ } catch {
15197
+ return String(error51);
15198
+ }
15199
+ }
15200
+ return String(error51);
15201
+ }
15202
+ function detectOverflow(error51) {
15203
+ const message = extractErrorMessage(error51);
15204
+ if (!message) {
15205
+ return { isOverflow: false };
15206
+ }
15207
+ const hasStatus413 = /\b413\b/.test(message) && /(entity|payload|context|prompt)/i.test(message);
15208
+ let matched;
15209
+ for (const pattern of OVERFLOW_PATTERNS) {
15210
+ if (pattern.test(message)) {
15211
+ matched = pattern;
15212
+ break;
15213
+ }
15214
+ }
15215
+ if (!matched && !hasStatus413) {
15216
+ return { isOverflow: false };
15217
+ }
15218
+ const reportedLimit = parseReportedLimit(message);
15219
+ return {
15220
+ isOverflow: true,
15221
+ reportedLimit,
15222
+ matchedPattern: matched?.source
15223
+ };
15224
+ }
15225
+ function parseReportedLimit(message) {
15226
+ if (!message)
15227
+ return;
15228
+ for (const pattern of LIMIT_EXTRACTION_PATTERNS) {
15229
+ const match = message.match(pattern);
15230
+ if (!match)
15231
+ continue;
15232
+ const raw = match[1];
15233
+ if (!raw)
15234
+ continue;
15235
+ const value = Number.parseInt(raw, 10);
15236
+ if (!Number.isFinite(value))
15237
+ continue;
15238
+ if (value < MIN_PLAUSIBLE_LIMIT || value > MAX_PLAUSIBLE_LIMIT)
15239
+ continue;
15240
+ return value;
15241
+ }
15242
+ return;
15243
+ }
15244
+ var OVERFLOW_PATTERNS, LIMIT_EXTRACTION_PATTERNS, MIN_PLAUSIBLE_LIMIT = 1024, MAX_PLAUSIBLE_LIMIT = 1e7;
15245
+ var init_overflow_detection = __esm(() => {
15246
+ OVERFLOW_PATTERNS = [
15247
+ /prompt is too long/i,
15248
+ /input is too long for requested model/i,
15249
+ /exceeds the context window/i,
15250
+ /input token count.*exceeds the maximum/i,
15251
+ /maximum prompt length is \d+/i,
15252
+ /reduce the length of the messages/i,
15253
+ /maximum context length is \d+ tokens/i,
15254
+ /exceeds the limit of \d+/i,
15255
+ /exceeds the available context size/i,
15256
+ /greater than the context length/i,
15257
+ /context window exceeds limit/i,
15258
+ /exceeded model token limit/i,
15259
+ /context[_ ]length[_ ]exceeded/i,
15260
+ /request entity too large/i,
15261
+ /context length is only \d+ tokens/i,
15262
+ /input length.*exceeds.*context length/i,
15263
+ /prompt too long; exceeded (?:max )?context length/i,
15264
+ /too large for model with \d+ maximum context length/i,
15265
+ /model_context_window_exceeded/i,
15266
+ /context size has been exceeded/i
15267
+ ];
15268
+ LIMIT_EXTRACTION_PATTERNS = [
15269
+ /maximum prompt length is (\d+)/i,
15270
+ /maximum context length is (\d+) tokens?/i,
15271
+ /context length is only (\d+) tokens?/i,
15272
+ /exceeds the limit of (\d+)/i,
15273
+ /too large for model with (\d+) maximum context length/i,
15274
+ /context size.*(\d+) tokens?/i,
15275
+ /exceeds? the context length of (\d+)/i,
15276
+ /max(?:imum)?.*context.*?(\d+)/i
15277
+ ];
15278
+ });
15279
+
15280
+ // src/shared/resolve-fallbacks.ts
15281
+ function resolveFallbackChain(agentName, userFallbacks) {
15282
+ const userList = normalizeUserFallbacks(userFallbacks);
15283
+ if (userList.length > 0) {
15284
+ return dedupe(userList.filter(isValidModelSpec));
15285
+ }
15286
+ const builtin = getAgentFallbackModels(agentName);
15287
+ if (!builtin || builtin.length === 0)
15288
+ return [];
15289
+ return dedupe(builtin.filter(isValidModelSpec));
15290
+ }
15291
+ function normalizeUserFallbacks(userFallbacks) {
15292
+ if (!userFallbacks)
15293
+ return [];
15294
+ if (typeof userFallbacks === "string") {
15295
+ const trimmed = userFallbacks.trim();
15296
+ return trimmed ? [trimmed] : [];
15297
+ }
15298
+ return userFallbacks.map((s) => s.trim()).filter((s) => s.length > 0);
15299
+ }
15300
+ function isValidModelSpec(spec) {
15301
+ const slash = spec.indexOf("/");
15302
+ return slash > 0 && slash < spec.length - 1;
15303
+ }
15304
+ function dedupe(list) {
15305
+ const seen = new Set;
15306
+ const out = [];
15307
+ for (const item of list) {
15308
+ if (seen.has(item))
15309
+ continue;
15310
+ seen.add(item);
15311
+ out.push(item);
15312
+ }
15313
+ return out;
15314
+ }
15315
+ function parseProviderModel(spec) {
15316
+ const slash = spec.indexOf("/");
15317
+ if (slash < 1 || slash >= spec.length - 1)
15318
+ return null;
15319
+ return {
15320
+ providerID: spec.slice(0, slash).trim(),
15321
+ modelID: spec.slice(slash + 1).trim()
15322
+ };
15323
+ }
15324
+ var init_resolve_fallbacks = __esm(() => {
15325
+ init_model_requirements();
15326
+ });
15327
+
15125
15328
  // src/shared/model-suggestion-retry.ts
15126
15329
  function extractMessage(error51) {
15127
15330
  if (typeof error51 === "string")
@@ -15177,6 +15380,9 @@ function parseModelSuggestion(error51) {
15177
15380
  };
15178
15381
  }
15179
15382
  async function promptWithTimeout(client, args, timeoutMs, signal) {
15383
+ if (signal?.aborted) {
15384
+ throw new Error("prompt aborted by external signal");
15385
+ }
15180
15386
  const controller = new AbortController;
15181
15387
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
15182
15388
  const onExternalAbort = () => controller.abort();
@@ -15199,16 +15405,39 @@ async function promptWithTimeout(client, args, timeoutMs, signal) {
15199
15405
  signal?.removeEventListener("abort", onExternalAbort);
15200
15406
  }
15201
15407
  }
15202
- async function promptSyncWithModelSuggestionRetry(client, args, options = {}) {
15203
- const timeoutMs = options.timeoutMs ?? 300000;
15408
+ function isNonRetryable(error51, externalSignal) {
15409
+ if (externalSignal?.aborted)
15410
+ return true;
15411
+ if (error51 instanceof Error) {
15412
+ if (error51.name === "AbortError")
15413
+ return true;
15414
+ if (error51.message === "prompt aborted by external signal")
15415
+ return true;
15416
+ if (/^prompt timed out after \d+ms$/.test(error51.message))
15417
+ return true;
15418
+ }
15419
+ if (detectOverflow(error51).isOverflow)
15420
+ return true;
15421
+ return false;
15422
+ }
15423
+ function shortErr(error51) {
15424
+ if (error51 instanceof Error) {
15425
+ return error51.name && error51.name !== "Error" ? `${error51.name}: ${error51.message}` : error51.message;
15426
+ }
15427
+ return extractMessage(error51);
15428
+ }
15429
+ async function attemptOnce(client, args, timeoutMs, signal, callContext, label) {
15204
15430
  try {
15205
- await promptWithTimeout(client, args, timeoutMs, options.signal);
15431
+ await promptWithTimeout(client, args, timeoutMs, signal);
15432
+ return;
15206
15433
  } catch (error51) {
15434
+ if (isNonRetryable(error51, signal))
15435
+ throw error51;
15207
15436
  const suggestion = parseModelSuggestion(error51);
15208
15437
  if (!suggestion || !args.body.model) {
15209
15438
  throw error51;
15210
15439
  }
15211
- log("[model-suggestion-retry] Model not found, retrying with suggestion", {
15440
+ log(`[${callContext}] ${label}: model not found, retrying with suggestion`, {
15212
15441
  original: `${suggestion.providerID}/${suggestion.modelID}`,
15213
15442
  suggested: suggestion.suggestion
15214
15443
  });
@@ -15221,11 +15450,59 @@ async function promptSyncWithModelSuggestionRetry(client, args, options = {}) {
15221
15450
  modelID: suggestion.suggestion
15222
15451
  }
15223
15452
  }
15224
- }, timeoutMs, options.signal);
15453
+ }, timeoutMs, signal);
15454
+ }
15455
+ }
15456
+ async function promptSyncWithModelSuggestionRetry(client, args, options = {}) {
15457
+ const timeoutMs = options.timeoutMs ?? 300000;
15458
+ const callContext = options.callContext ?? "subagent";
15459
+ const fallbacks = options.fallbackModels ?? [];
15460
+ const explicitPrimaryLabel = args.body.model?.providerID && args.body.model.modelID ? `${args.body.model.providerID}/${args.body.model.modelID}` : "primary";
15461
+ let lastError = null;
15462
+ try {
15463
+ await attemptOnce(client, args, timeoutMs, options.signal, callContext, explicitPrimaryLabel);
15464
+ return;
15465
+ } catch (error51) {
15466
+ lastError = error51;
15467
+ if (isNonRetryable(error51, options.signal))
15468
+ throw error51;
15469
+ if (fallbacks.length === 0) {
15470
+ throw error51;
15471
+ }
15472
+ log(`[${callContext}] primary (${explicitPrimaryLabel}) failed: ${shortErr(error51)}; trying ${fallbacks.length} fallback(s)`);
15473
+ }
15474
+ for (let i = 0;i < fallbacks.length; i += 1) {
15475
+ const parsed = parseProviderModel(fallbacks[i]);
15476
+ if (!parsed) {
15477
+ log(`[${callContext}] skipping invalid fallback spec: ${fallbacks[i]}`);
15478
+ continue;
15479
+ }
15480
+ const label = `${parsed.providerID}/${parsed.modelID}`;
15481
+ const attemptArgs = {
15482
+ ...args,
15483
+ body: { ...args.body, model: parsed }
15484
+ };
15485
+ try {
15486
+ await attemptOnce(client, attemptArgs, timeoutMs, options.signal, callContext, label);
15487
+ log(`[${callContext}] fallback succeeded with ${label} (attempt ${i + 2}/${fallbacks.length + 1})`);
15488
+ return;
15489
+ } catch (error51) {
15490
+ lastError = error51;
15491
+ if (isNonRetryable(error51, options.signal))
15492
+ throw error51;
15493
+ const remaining = fallbacks.length - i - 1;
15494
+ if (remaining > 0) {
15495
+ log(`[${callContext}] ${label} failed: ${shortErr(error51)}; ${remaining} fallback(s) left`);
15496
+ }
15497
+ }
15225
15498
  }
15499
+ log(`[${callContext}] all models exhausted; tried: ${[explicitPrimaryLabel, ...fallbacks].join(", ")}; last error: ${shortErr(lastError)}`);
15500
+ throw lastError ?? new Error("All fallback models failed");
15226
15501
  }
15227
15502
  var init_model_suggestion_retry = __esm(() => {
15503
+ init_overflow_detection();
15228
15504
  init_logger();
15505
+ init_resolve_fallbacks();
15229
15506
  });
15230
15507
 
15231
15508
  // src/shared/normalize-sdk-response.ts
@@ -15257,6 +15534,7 @@ var init_shared = __esm(() => {
15257
15534
  init_logger();
15258
15535
  init_model_requirements();
15259
15536
  init_model_suggestion_retry();
15537
+ init_resolve_fallbacks();
15260
15538
  });
15261
15539
 
15262
15540
  // src/shared/record-type-guard.ts
@@ -147908,21 +148186,36 @@ var init_read_session_formatting = __esm(() => {
147908
148186
  tokenizer = new src_default(exports_claude);
147909
148187
  });
147910
148188
 
148189
+ // src/shared/stable-json.ts
148190
+ function stableStringify(value, seen = new WeakSet) {
148191
+ if (value === undefined)
148192
+ return "undefined";
148193
+ if (value === null || typeof value !== "object")
148194
+ return JSON.stringify(value) ?? String(value);
148195
+ if (seen.has(value))
148196
+ return '"[Circular]"';
148197
+ seen.add(value);
148198
+ if (Array.isArray(value)) {
148199
+ return `[${value.map((item) => stableStringify(item, seen)).join(",")}]`;
148200
+ }
148201
+ const entries = Object.entries(value).sort(([a], [b]) => {
148202
+ if (a < b)
148203
+ return -1;
148204
+ if (a > b)
148205
+ return 1;
148206
+ return 0;
148207
+ });
148208
+ return `{${entries.map(([key, child]) => `${JSON.stringify(key)}:${stableStringify(child, seen)}`).join(",")}}`;
148209
+ }
148210
+
147911
148211
  // src/features/magic-context/tool-definition-tokens.ts
148212
+ import { createHash } from "node:crypto";
147912
148213
  function keyFor(providerID, modelID, agentName) {
147913
148214
  const agent = agentName && agentName.length > 0 ? agentName : "default";
147914
148215
  return `${providerID}/${modelID}/${agent}`;
147915
148216
  }
147916
148217
  function fingerprintFor(description, parameters) {
147917
- const descLen = description.length;
147918
- if (parameters === undefined)
147919
- return `${descLen}:none`;
147920
- if (parameters === null)
147921
- return `${descLen}:null`;
147922
- if (typeof parameters !== "object")
147923
- return `${descLen}:${typeof parameters}`;
147924
- const keys = Object.keys(parameters);
147925
- return `${descLen}:obj:${keys.length}:${keys.sort().join(",")}`;
148218
+ return createHash("sha256").update(description).update("\x00").update(stableStringify(parameters)).digest("hex");
147926
148219
  }
147927
148220
  function setDatabase(db) {
147928
148221
  persistenceDb = db;
@@ -155760,12 +156053,6 @@ var require_src2 = __commonJS((exports, module) => {
155760
156053
  };
155761
156054
  });
155762
156055
 
155763
- // src/shared/harness.ts
155764
- function getHarness() {
155765
- return currentHarness;
155766
- }
155767
- var currentHarness = "opencode";
155768
-
155769
156056
  // src/features/magic-context/compartment-storage.ts
155770
156057
  function getInsertCompartmentStatement(db) {
155771
156058
  let stmt = insertCompartmentStatements.get(db);
@@ -155839,6 +156126,10 @@ function getLastCompartmentEndMessage(db, sessionId) {
155839
156126
  const row = db.prepare("SELECT MAX(end_message) as max_end FROM compartments WHERE session_id = ?").get(sessionId);
155840
156127
  return row?.max_end ?? -1;
155841
156128
  }
156129
+ function getCompartmentsByEndMessageId(db, sessionId, endMessageId) {
156130
+ const rows = db.prepare("SELECT * FROM compartments WHERE session_id = ? AND end_message_id = ? ORDER BY sequence ASC").all(sessionId, endMessageId).filter(isCompartmentRow);
156131
+ return rows.map(toCompartment);
156132
+ }
155842
156133
  function appendCompartments(db, sessionId, compartments) {
155843
156134
  if (compartments.length === 0)
155844
156135
  return;
@@ -156342,7 +156633,7 @@ var init_compartment_prompt = __esm(() => {
156342
156633
  });
156343
156634
 
156344
156635
  // src/shared/opencode-config-dir.ts
156345
- import { homedir as homedir5 } from "node:os";
156636
+ import { homedir as homedir6 } from "node:os";
156346
156637
  import { join as join7, resolve as resolve3 } from "node:path";
156347
156638
  function getCliConfigDir() {
156348
156639
  const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
@@ -156350,9 +156641,9 @@ function getCliConfigDir() {
156350
156641
  return resolve3(envConfigDir);
156351
156642
  }
156352
156643
  if (process.platform === "win32") {
156353
- return join7(homedir5(), ".config", "opencode");
156644
+ return join7(homedir6(), ".config", "opencode");
156354
156645
  }
156355
- return join7(process.env.XDG_CONFIG_HOME || join7(homedir5(), ".config"), "opencode");
156646
+ return join7(process.env.XDG_CONFIG_HOME || join7(homedir6(), ".config"), "opencode");
156356
156647
  }
156357
156648
  function getOpenCodeConfigDir(_options) {
156358
156649
  return getCliConfigDir();
@@ -156583,11 +156874,11 @@ __export(exports_conflict_warning_hook, {
156583
156874
  cleanupConflictWarnings: () => cleanupConflictWarnings
156584
156875
  });
156585
156876
  import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
156586
- import { homedir as homedir6, platform as platform2 } from "node:os";
156877
+ import { homedir as homedir7, platform as platform2 } from "node:os";
156587
156878
  import { join as join9 } from "node:path";
156588
156879
  function getDesktopStatePath() {
156589
156880
  const os2 = platform2();
156590
- const home = homedir6();
156881
+ const home = homedir7();
156591
156882
  if (os2 === "darwin") {
156592
156883
  return join9(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
156593
156884
  }
@@ -156866,26 +157157,6 @@ var init_conflict_warning_hook = __esm(() => {
156866
157157
  cachedDesktopStateByDir = new Map;
156867
157158
  });
156868
157159
 
156869
- // src/shared/data-path.ts
156870
- import * as os2 from "node:os";
156871
- import * as path2 from "node:path";
156872
- function getDataDir() {
156873
- return process.env.XDG_DATA_HOME ?? path2.join(os2.homedir(), ".local", "share");
156874
- }
156875
- function getOpenCodeStorageDir() {
156876
- return path2.join(getDataDir(), "opencode", "storage");
156877
- }
156878
- function getMagicContextStorageDir() {
156879
- return path2.join(getDataDir(), "cortexkit", "magic-context");
156880
- }
156881
- function getLegacyOpenCodeMagicContextStorageDir() {
156882
- return path2.join(getOpenCodeStorageDir(), "plugin", "magic-context");
156883
- }
156884
- function getCacheDir() {
156885
- return process.env.XDG_CACHE_HOME ?? path2.join(os2.homedir(), ".cache");
156886
- }
156887
- var init_data_path = () => {};
156888
-
156889
157160
  // src/shared/error-message.ts
156890
157161
  function getErrorMessage(error51) {
156891
157162
  return error51 instanceof Error ? error51.message : String(error51);
@@ -157143,7 +157414,7 @@ var exports_native_binding = {};
157143
157414
  __export(exports_native_binding, {
157144
157415
  resolveBetterSqliteNativeBinding: () => resolveBetterSqliteNativeBinding
157145
157416
  });
157146
- import { existsSync as existsSync8, mkdirSync as mkdirSync2, writeFileSync as writeFileSync4 } from "node:fs";
157417
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "node:fs";
157147
157418
  import { createRequire } from "node:module";
157148
157419
  import * as path3 from "node:path";
157149
157420
  function logInfo(message) {
@@ -157243,7 +157514,7 @@ async function resolveBetterSqliteNativeBinding() {
157243
157514
  }
157244
157515
  logWarn(`cached binary at ${cachedPath} has wrong ABI (${cachedProbe.actual ?? "unknown"} != ${expected}); refetching`);
157245
157516
  }
157246
- mkdirSync2(path3.dirname(cachedPath), { recursive: true });
157517
+ mkdirSync3(path3.dirname(cachedPath), { recursive: true });
157247
157518
  const nodeFileBytes = await downloadElectronPrebuild(pkgVersion, expected);
157248
157519
  writeFileSync4(cachedPath, nodeFileBytes);
157249
157520
  logInfo(`cached Electron prebuild at ${cachedPath} (${(nodeFileBytes.length / 1024).toFixed(1)} KB)`);
@@ -157421,13 +157692,13 @@ var init_embedding_cache = __esm(() => {
157421
157692
  });
157422
157693
 
157423
157694
  // src/features/magic-context/memory/normalize-hash.ts
157424
- import { createHash } from "node:crypto";
157695
+ import { createHash as createHash2 } from "node:crypto";
157425
157696
  function normalizeMemoryContent(content) {
157426
157697
  return content.toLowerCase().replace(/\s+/g, " ").trim();
157427
157698
  }
157428
157699
  function computeNormalizedHash(content) {
157429
157700
  const normalized = normalizeMemoryContent(content);
157430
- return createHash("md5").update(normalized).digest("hex");
157701
+ return createHash2("md5").update(normalized).digest("hex");
157431
157702
  }
157432
157703
  var init_normalize_hash = () => {};
157433
157704
 
@@ -158021,9 +158292,9 @@ __export(exports_read_session_db, {
158021
158292
  findLastAssistantModelFromOpenCodeDb: () => findLastAssistantModelFromOpenCodeDb,
158022
158293
  closeReadOnlySessionDb: () => closeReadOnlySessionDb
158023
158294
  });
158024
- import { join as join12 } from "node:path";
158295
+ import { join as join11 } from "node:path";
158025
158296
  function getOpenCodeDbPath() {
158026
- return join12(getDataDir(), "opencode", "opencode.db");
158297
+ return join11(getDataDir(), "opencode", "opencode.db");
158027
158298
  }
158028
158299
  function closeCachedReadOnlyDb() {
158029
158300
  if (!cachedReadOnlyDb) {
@@ -158130,10 +158401,36 @@ function cosineSimilarity(a, b) {
158130
158401
  return denominator === 0 ? 0 : dotProduct / denominator;
158131
158402
  }
158132
158403
 
158404
+ // src/features/magic-context/memory/embedding-identity.ts
158405
+ function normalizeEndpoint(endpoint) {
158406
+ return endpoint?.trim().replace(/\/+$/, "") ?? "";
158407
+ }
158408
+ function getEmbeddingProviderIdentity(config2) {
158409
+ if (config2.provider === "off") {
158410
+ return "embedding-provider:off";
158411
+ }
158412
+ const identityInput = config2.provider === "openai-compatible" ? {
158413
+ provider: "openai-compatible",
158414
+ model: config2.model.trim(),
158415
+ endpoint: normalizeEndpoint(config2.endpoint),
158416
+ apiKeyPresent: Boolean(config2.api_key?.trim())
158417
+ } : {
158418
+ provider: "local",
158419
+ model: config2.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
158420
+ endpoint: "",
158421
+ apiKeyPresent: false
158422
+ };
158423
+ return `embedding-provider:${computeNormalizedHash(JSON.stringify(identityInput))}`;
158424
+ }
158425
+ var init_embedding_identity = __esm(() => {
158426
+ init_magic_context();
158427
+ init_normalize_hash();
158428
+ });
158429
+
158133
158430
  // src/features/magic-context/memory/embedding-local.ts
158134
- import { mkdirSync as mkdirSync3 } from "node:fs";
158431
+ import { mkdirSync as mkdirSync4 } from "node:fs";
158135
158432
  import { open, stat, unlink, writeFile } from "node:fs/promises";
158136
- import { join as join14 } from "node:path";
158433
+ import { join as join13 } from "node:path";
158137
158434
  async function acquireModelLoadLock(lockPath) {
158138
158435
  const waitStart = Date.now();
158139
158436
  while (true) {
@@ -158248,7 +158545,7 @@ class LocalEmbeddingProvider {
158248
158545
  initPromise = null;
158249
158546
  constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL) {
158250
158547
  this.model = model;
158251
- this.modelId = `local:${model}`;
158548
+ this.modelId = getEmbeddingProviderIdentity({ provider: "local", model });
158252
158549
  }
158253
158550
  async initialize() {
158254
158551
  if (this.pipeline) {
@@ -158267,15 +158564,15 @@ class LocalEmbeddingProvider {
158267
158564
  if (LogLevel && "ERROR" in LogLevel) {
158268
158565
  env.logLevel = LogLevel.ERROR;
158269
158566
  }
158270
- const modelCacheDir = join14(getMagicContextStorageDir(), "models");
158567
+ const modelCacheDir = join13(getMagicContextStorageDir(), "models");
158271
158568
  try {
158272
- mkdirSync3(modelCacheDir, { recursive: true });
158569
+ mkdirSync4(modelCacheDir, { recursive: true });
158273
158570
  env.cacheDir = modelCacheDir;
158274
158571
  } catch {
158275
158572
  log("[magic-context] could not create model cache dir, using library default");
158276
158573
  }
158277
158574
  const createPipeline = transformersModule.pipeline;
158278
- const lockPath = join14(modelCacheDir, ".load.lock");
158575
+ const lockPath = join13(modelCacheDir, ".load.lock");
158279
158576
  const releaseLock = await acquireModelLoadLock(lockPath);
158280
158577
  const stopHeartbeat = startLockHeartbeat(lockPath);
158281
158578
  try {
@@ -158390,12 +158687,13 @@ var init_embedding_local = __esm(() => {
158390
158687
  init_magic_context();
158391
158688
  init_data_path();
158392
158689
  init_logger();
158690
+ init_embedding_identity();
158393
158691
  STALE_LOCK_MS = 3 * 60000;
158394
158692
  MAX_LOCK_WAIT_MS = 5 * 60000;
158395
158693
  });
158396
158694
 
158397
158695
  // src/features/magic-context/memory/embedding-openai.ts
158398
- function normalizeEndpoint(endpoint) {
158696
+ function normalizeEndpoint2(endpoint) {
158399
158697
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
158400
158698
  }
158401
158699
 
@@ -158410,10 +158708,15 @@ class OpenAICompatibleEmbeddingProvider {
158410
158708
  openLogged = false;
158411
158709
  halfOpenProbeInFlight = false;
158412
158710
  constructor(options) {
158413
- this.endpoint = normalizeEndpoint(options.endpoint);
158711
+ this.endpoint = normalizeEndpoint2(options.endpoint);
158414
158712
  this.model = options.model?.trim() ?? "";
158415
158713
  this.apiKey = options.apiKey?.trim() ?? "";
158416
- this.modelId = `openai-compat:${this.endpoint}:${this.model}`;
158714
+ this.modelId = getEmbeddingProviderIdentity({
158715
+ provider: "openai-compatible",
158716
+ endpoint: this.endpoint,
158717
+ model: this.model,
158718
+ ...this.apiKey ? { api_key: this.apiKey } : {}
158719
+ });
158417
158720
  }
158418
158721
  async initialize() {
158419
158722
  if (this.initialized)
@@ -158595,6 +158898,7 @@ class OpenAICompatibleEmbeddingProvider {
158595
158898
  var FAILURE_THRESHOLD = 3, FAILURE_WINDOW_MS = 60000, OPEN_DURATION_MS, FETCH_TIMEOUT_MS = 30000;
158596
158899
  var init_embedding_openai = __esm(() => {
158597
158900
  init_logger();
158901
+ init_embedding_identity();
158598
158902
  OPEN_DURATION_MS = 5 * 60000;
158599
158903
  });
158600
158904
 
@@ -158632,17 +158936,8 @@ function resolveEmbeddingConfig(config2) {
158632
158936
  }
158633
158937
  return { provider: "off" };
158634
158938
  }
158635
- function resolveModelId(config2) {
158636
- if (config2.provider === "off") {
158637
- return "off";
158638
- }
158639
- if (config2.provider === "openai-compatible") {
158640
- const endpoint = config2.endpoint.trim();
158641
- const model = config2.model.trim();
158642
- const keyHash = config2.api_key ? computeNormalizedHash(config2.api_key) : "nokey";
158643
- return `openai-compat:${endpoint}:${model}:${keyHash}`;
158644
- }
158645
- return config2.model.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL;
158939
+ function resolveProviderIdentity(config2) {
158940
+ return getEmbeddingProviderIdentity(config2);
158646
158941
  }
158647
158942
  function createProvider(config2) {
158648
158943
  if (config2.provider === "off") {
@@ -158666,10 +158961,10 @@ function getOrCreateProvider() {
158666
158961
  }
158667
158962
  function initializeEmbedding(config2) {
158668
158963
  const nextConfig = resolveEmbeddingConfig(config2);
158669
- const nextModelId = resolveModelId(nextConfig);
158964
+ const nextProviderIdentity = resolveProviderIdentity(nextConfig);
158670
158965
  const previousProvider = provider;
158671
- const previousModelId = previousProvider?.modelId ?? resolveModelId(embeddingConfig);
158672
- if (previousModelId === nextModelId) {
158966
+ const previousProviderIdentity = previousProvider?.modelId ?? resolveProviderIdentity(embeddingConfig);
158967
+ if (previousProviderIdentity === nextProviderIdentity) {
158673
158968
  embeddingConfig = nextConfig;
158674
158969
  return;
158675
158970
  }
@@ -158796,9 +159091,9 @@ var DEFAULT_EMBEDDING_CONFIG, embeddingConfig, provider = null, loadUnembeddedMe
158796
159091
  var init_embedding = __esm(() => {
158797
159092
  init_magic_context();
158798
159093
  init_logger();
159094
+ init_embedding_identity();
158799
159095
  init_embedding_local();
158800
159096
  init_embedding_openai();
158801
- init_normalize_hash();
158802
159097
  init_storage_memory_embeddings();
158803
159098
  DEFAULT_EMBEDDING_CONFIG = {
158804
159099
  provider: "local",
@@ -158845,7 +159140,7 @@ var init_storage_memory_fts = __esm(() => {
158845
159140
 
158846
159141
  // src/features/magic-context/memory/project-identity.ts
158847
159142
  import { execSync } from "node:child_process";
158848
- import { createHash as createHash2 } from "node:crypto";
159143
+ import { createHash as createHash3 } from "node:crypto";
158849
159144
  import path4 from "node:path";
158850
159145
  function getRootCommitHash(directory) {
158851
159146
  try {
@@ -158864,7 +159159,7 @@ function getRootCommitHash(directory) {
158864
159159
  }
158865
159160
  function directoryFallback(directory) {
158866
159161
  const canonical = path4.resolve(directory);
158867
- const hash2 = createHash2("md5").update(canonical).digest("hex").slice(0, 12);
159162
+ const hash2 = createHash3("md5").update(canonical).digest("hex").slice(0, 12);
158868
159163
  return `dir:${hash2}`;
158869
159164
  }
158870
159165
  function resolveProjectIdentity(directory) {
@@ -159914,6 +160209,9 @@ var init_migrations = __esm(async () => {
159914
160209
  embedding BLOB NOT NULL,
159915
160210
  model_id TEXT NOT NULL,
159916
160211
  created_at INTEGER NOT NULL,
160212
+ -- FK-cascade audit (v12): git_commit_embeddings.sha -> git_commits.sha
160213
+ -- uses ON DELETE CASCADE, so SQLite PRAGMA foreign_keys must be ON on
160214
+ -- every connection and v12 cleans historical orphan rows.
159917
160215
  FOREIGN KEY(sha) REFERENCES git_commits(sha) ON DELETE CASCADE
159918
160216
  );
159919
160217
 
@@ -160034,15 +160332,57 @@ var init_migrations = __esm(async () => {
160034
160332
  WHERE type = 'tool' AND tool_owner_message_id IS NULL;
160035
160333
  `);
160036
160334
  }
160335
+ },
160336
+ {
160337
+ version: 11,
160338
+ description: "Add todo state synthesis columns to session_meta",
160339
+ up: (db) => {
160340
+ const cols = db.prepare("PRAGMA table_info(session_meta)").all();
160341
+ if (!cols.some((c) => c.name === "last_todo_state")) {
160342
+ db.exec("ALTER TABLE session_meta ADD COLUMN last_todo_state TEXT DEFAULT ''");
160343
+ }
160344
+ if (!cols.some((c) => c.name === "todo_synthetic_call_id")) {
160345
+ db.exec("ALTER TABLE session_meta ADD COLUMN todo_synthetic_call_id TEXT DEFAULT ''");
160346
+ }
160347
+ if (!cols.some((c) => c.name === "todo_synthetic_anchor_message_id")) {
160348
+ db.exec("ALTER TABLE session_meta ADD COLUMN todo_synthetic_anchor_message_id TEXT DEFAULT ''");
160349
+ }
160350
+ if (!cols.some((c) => c.name === "todo_synthetic_state_json")) {
160351
+ db.exec("ALTER TABLE session_meta ADD COLUMN todo_synthetic_state_json TEXT DEFAULT ''");
160352
+ }
160353
+ }
160354
+ },
160355
+ {
160356
+ version: 12,
160357
+ description: "Clean orphan rows from FK-cascade embedding tables",
160358
+ up: (db) => {
160359
+ const hasTable = (name2) => Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name2));
160360
+ const memoryEmbeddings = hasTable("memory_embeddings") ? db.prepare(`DELETE FROM memory_embeddings
160361
+ WHERE memory_id NOT IN (SELECT id FROM memories)`).run().changes : 0;
160362
+ log(`[migrations] v12 cleaned ${memoryEmbeddings} orphan memory_embeddings row(s)`);
160363
+ const gitCommitEmbeddings = hasTable("git_commit_embeddings") ? db.prepare(`DELETE FROM git_commit_embeddings
160364
+ WHERE sha NOT IN (SELECT sha FROM git_commits)`).run().changes : 0;
160365
+ log(`[migrations] v12 cleaned ${gitCommitEmbeddings} orphan git_commit_embeddings row(s)`);
160366
+ }
160367
+ },
160368
+ {
160369
+ version: 13,
160370
+ description: "Add pending_compaction_marker_state column for deferred marker drain",
160371
+ up: (db) => {
160372
+ const cols = db.prepare("PRAGMA table_info(session_meta)").all();
160373
+ if (!cols.some((c) => c.name === "pending_compaction_marker_state")) {
160374
+ db.exec("ALTER TABLE session_meta ADD COLUMN pending_compaction_marker_state TEXT");
160375
+ }
160376
+ }
160037
160377
  }
160038
160378
  ];
160039
160379
  });
160040
160380
 
160041
160381
  // src/features/magic-context/tool-owner-backfill.ts
160042
160382
  import { existsSync as existsSync10 } from "node:fs";
160043
- import { join as join15 } from "node:path";
160383
+ import { join as join14 } from "node:path";
160044
160384
  function resolveOpencodeDbPath() {
160045
- return join15(getDataDir(), "opencode", "opencode.db");
160385
+ return join14(getDataDir(), "opencode", "opencode.db");
160046
160386
  }
160047
160387
  function ensureBackfillStateTable(db) {
160048
160388
  db.exec(`
@@ -160287,24 +160627,24 @@ var init_tool_owner_backfill = __esm(() => {
160287
160627
  });
160288
160628
 
160289
160629
  // src/features/magic-context/storage-db.ts
160290
- import { copyFileSync, cpSync, existsSync as existsSync11, mkdirSync as mkdirSync4 } from "node:fs";
160291
- import { join as join16 } from "node:path";
160630
+ import { copyFileSync, cpSync, existsSync as existsSync11, mkdirSync as mkdirSync5 } from "node:fs";
160631
+ import { join as join15 } from "node:path";
160292
160632
  function resolveDatabasePath() {
160293
160633
  const dbDir = getMagicContextStorageDir();
160294
- return { dbDir, dbPath: join16(dbDir, "context.db") };
160634
+ return { dbDir, dbPath: join15(dbDir, "context.db") };
160295
160635
  }
160296
160636
  function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
160297
160637
  if (existsSync11(targetDbPath))
160298
160638
  return;
160299
160639
  const legacyDir = getLegacyOpenCodeMagicContextStorageDir();
160300
- const legacyDbPath = join16(legacyDir, "context.db");
160640
+ const legacyDbPath = join15(legacyDir, "context.db");
160301
160641
  if (!existsSync11(legacyDbPath))
160302
160642
  return;
160303
160643
  log(`[magic-context] migrating legacy plugin storage: ${legacyDir} -> ${targetDbDir} (legacy left in place as backup)`);
160304
- mkdirSync4(targetDbDir, { recursive: true });
160644
+ mkdirSync5(targetDbDir, { recursive: true });
160305
160645
  for (const suffix of ["", "-wal", "-shm"]) {
160306
160646
  const src = `${legacyDbPath}${suffix}`;
160307
- const dst = join16(targetDbDir, `context.db${suffix}`);
160647
+ const dst = join15(targetDbDir, `context.db${suffix}`);
160308
160648
  if (existsSync11(src)) {
160309
160649
  try {
160310
160650
  copyFileSync(src, dst);
@@ -160313,8 +160653,8 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
160313
160653
  }
160314
160654
  }
160315
160655
  }
160316
- const legacyModelsDir = join16(legacyDir, "models");
160317
- const targetModelsDir = join16(targetDbDir, "models");
160656
+ const legacyModelsDir = join15(legacyDir, "models");
160657
+ const targetModelsDir = join15(targetDbDir, "models");
160318
160658
  if (existsSync11(legacyModelsDir) && !existsSync11(targetModelsDir)) {
160319
160659
  try {
160320
160660
  cpSync(legacyModelsDir, targetModelsDir, { recursive: true });
@@ -160324,9 +160664,9 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
160324
160664
  }
160325
160665
  }
160326
160666
  function initializeDatabase(db) {
160667
+ db.exec("PRAGMA foreign_keys=ON");
160327
160668
  db.exec("PRAGMA journal_mode=WAL");
160328
160669
  db.exec("PRAGMA busy_timeout=5000");
160329
- db.exec("PRAGMA foreign_keys=ON");
160330
160670
  db.exec(`
160331
160671
  CREATE TABLE IF NOT EXISTS tags (
160332
160672
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -160424,6 +160764,9 @@ function initializeDatabase(db) {
160424
160764
  );
160425
160765
 
160426
160766
  CREATE TABLE IF NOT EXISTS memory_embeddings (
160767
+ -- FK-cascade audit (v12): memory_embeddings.memory_id -> memories.id
160768
+ -- uses ON DELETE CASCADE, so SQLite PRAGMA foreign_keys must be ON on
160769
+ -- every connection and v12 cleans historical orphan rows.
160427
160770
  memory_id INTEGER PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
160428
160771
  embedding BLOB NOT NULL,
160429
160772
  model_id TEXT
@@ -160516,6 +160859,10 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
160516
160859
  note_nudge_trigger_message_id TEXT DEFAULT '',
160517
160860
  note_nudge_sticky_text TEXT DEFAULT '',
160518
160861
  note_nudge_sticky_message_id TEXT DEFAULT '',
160862
+ last_todo_state TEXT DEFAULT '',
160863
+ todo_synthetic_call_id TEXT DEFAULT '',
160864
+ todo_synthetic_anchor_message_id TEXT DEFAULT '',
160865
+ todo_synthetic_state_json TEXT DEFAULT '',
160519
160866
  is_subagent INTEGER DEFAULT 0,
160520
160867
  last_context_percentage REAL DEFAULT 0,
160521
160868
  last_input_tokens INTEGER DEFAULT 0,
@@ -160527,7 +160874,13 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
160527
160874
  system_prompt_hash TEXT DEFAULT '',
160528
160875
  memory_block_cache TEXT DEFAULT '',
160529
160876
  memory_block_count INTEGER DEFAULT 0,
160530
- memory_block_ids TEXT DEFAULT ''
160877
+ memory_block_ids TEXT DEFAULT '',
160878
+ -- pending_compaction_marker_state: intentionally NULLABLE without a
160879
+ -- default. Absence of a deferred marker is SQL NULL; presence is a
160880
+ -- valid JSON blob written via setPendingCompactionMarkerState.
160881
+ -- Excluded from healNullTextColumns. Readers filter IS NOT NULL AND
160882
+ -- != empty-string defensively. Plan v6 section 3.
160883
+ pending_compaction_marker_state TEXT
160531
160884
  );
160532
160885
 
160533
160886
  CREATE INDEX IF NOT EXISTS idx_tags_session_tag_number ON tags(session_id, tag_number);
@@ -160580,6 +160933,10 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
160580
160933
  ensureColumn(db, "session_meta", "note_nudge_trigger_message_id", "TEXT DEFAULT ''");
160581
160934
  ensureColumn(db, "session_meta", "note_nudge_sticky_text", "TEXT DEFAULT ''");
160582
160935
  ensureColumn(db, "session_meta", "note_nudge_sticky_message_id", "TEXT DEFAULT ''");
160936
+ ensureColumn(db, "session_meta", "last_todo_state", "TEXT DEFAULT ''");
160937
+ ensureColumn(db, "session_meta", "todo_synthetic_call_id", "TEXT DEFAULT ''");
160938
+ ensureColumn(db, "session_meta", "todo_synthetic_anchor_message_id", "TEXT DEFAULT ''");
160939
+ ensureColumn(db, "session_meta", "todo_synthetic_state_json", "TEXT DEFAULT ''");
160583
160940
  ensureColumn(db, "session_meta", "note_last_read_at", "INTEGER DEFAULT 0");
160584
160941
  ensureColumn(db, "session_meta", "times_execute_threshold_reached", "INTEGER DEFAULT 0");
160585
160942
  ensureColumn(db, "session_meta", "compartment_in_progress", "INTEGER DEFAULT 0");
@@ -160611,6 +160968,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
160611
160968
  ensureColumn(db, "session_meta", "recomp_partial_range_end", "INTEGER DEFAULT 0");
160612
160969
  ensureColumn(db, "session_meta", "detected_context_limit", "INTEGER DEFAULT 0");
160613
160970
  ensureColumn(db, "session_meta", "needs_emergency_recovery", "INTEGER DEFAULT 0");
160971
+ ensureColumn(db, "session_meta", "pending_compaction_marker_state", "TEXT");
160614
160972
  ensureColumn(db, "tags", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
160615
160973
  ensureColumn(db, "pending_ops", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
160616
160974
  ensureColumn(db, "source_contents", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
@@ -160644,6 +161002,10 @@ function healNullTextColumns(db) {
160644
161002
  ["note_nudge_trigger_message_id", ""],
160645
161003
  ["note_nudge_sticky_text", ""],
160646
161004
  ["note_nudge_sticky_message_id", ""],
161005
+ ["last_todo_state", ""],
161006
+ ["todo_synthetic_call_id", ""],
161007
+ ["todo_synthetic_anchor_message_id", ""],
161008
+ ["todo_synthetic_state_json", ""],
160647
161009
  ["system_prompt_hash", ""],
160648
161010
  ["stripped_placeholder_ids", ""],
160649
161011
  ["memory_block_cache", ""],
@@ -160696,7 +161058,7 @@ function openDatabase() {
160696
161058
  }
160697
161059
  try {
160698
161060
  migrateLegacyStorageIfNeeded(dbPath, dbDir);
160699
- mkdirSync4(dbDir, { recursive: true });
161061
+ mkdirSync5(dbDir, { recursive: true });
160700
161062
  const db = new Database(dbPath);
160701
161063
  initializeDatabase(db);
160702
161064
  runMigrations(db);
@@ -160749,7 +161111,7 @@ function isSessionMetaRow(row) {
160749
161111
  if (row === null || typeof row !== "object")
160750
161112
  return false;
160751
161113
  const r = row;
160752
- return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag);
161114
+ return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state);
160753
161115
  }
160754
161116
  function getDefaultSessionMeta(sessionId) {
160755
161117
  return {
@@ -160769,7 +161131,8 @@ function getDefaultSessionMeta(sessionId) {
160769
161131
  systemPromptTokens: 0,
160770
161132
  conversationTokens: 0,
160771
161133
  toolCallTokens: 0,
160772
- clearedReasoningThroughTag: 0
161134
+ clearedReasoningThroughTag: 0,
161135
+ lastTodoState: ""
160773
161136
  };
160774
161137
  }
160775
161138
  function ensureSessionMetaRow(db, sessionId) {
@@ -160781,6 +161144,7 @@ function toSessionMeta(row) {
160781
161144
  const transformErrorRaw = typeof row.last_transform_error === "string" ? row.last_transform_error : "";
160782
161145
  const cacheTtlRaw = typeof row.cache_ttl === "string" && row.cache_ttl.length > 0 ? row.cache_ttl : "5m";
160783
161146
  const systemPromptHashRaw = row.system_prompt_hash == null ? "" : row.system_prompt_hash;
161147
+ const lastTodoStateRaw = typeof row.last_todo_state === "string" ? row.last_todo_state : "";
160784
161148
  const numOrZero = (value) => typeof value === "number" ? value : 0;
160785
161149
  return {
160786
161150
  sessionId: row.session_id,
@@ -160799,7 +161163,8 @@ function toSessionMeta(row) {
160799
161163
  systemPromptTokens: numOrZero(row.system_prompt_tokens),
160800
161164
  conversationTokens: numOrZero(row.conversation_tokens),
160801
161165
  toolCallTokens: numOrZero(row.tool_call_tokens),
160802
- clearedReasoningThroughTag: numOrZero(row.cleared_reasoning_through_tag)
161166
+ clearedReasoningThroughTag: numOrZero(row.cleared_reasoning_through_tag),
161167
+ lastTodoState: lastTodoStateRaw
160803
161168
  };
160804
161169
  }
160805
161170
  var META_COLUMNS, BOOLEAN_META_KEYS;
@@ -160820,7 +161185,8 @@ var init_storage_meta_shared = __esm(() => {
160820
161185
  systemPromptTokens: "system_prompt_tokens",
160821
161186
  conversationTokens: "conversation_tokens",
160822
161187
  toolCallTokens: "tool_call_tokens",
160823
- clearedReasoningThroughTag: "cleared_reasoning_through_tag"
161188
+ clearedReasoningThroughTag: "cleared_reasoning_through_tag",
161189
+ lastTodoState: "last_todo_state"
160824
161190
  };
160825
161191
  BOOLEAN_META_KEYS = new Set(["isSubagent", "compartmentInProgress"]);
160826
161192
  });
@@ -160856,6 +161222,12 @@ function isPersistedNoteNudgeRow(row) {
160856
161222
  const r = row;
160857
161223
  return typeof r.note_nudge_trigger_pending === "number" && typeof r.note_nudge_trigger_message_id === "string" && typeof r.note_nudge_sticky_text === "string" && typeof r.note_nudge_sticky_message_id === "string";
160858
161224
  }
161225
+ function isPersistedTodoSyntheticAnchorRow(row) {
161226
+ if (row === null || typeof row !== "object")
161227
+ return false;
161228
+ const r = row;
161229
+ return typeof r.todo_synthetic_call_id === "string" && typeof r.todo_synthetic_anchor_message_id === "string" && typeof r.todo_synthetic_state_json === "string";
161230
+ }
160859
161231
  function isPersistedHistorianFailureRow(row) {
160860
161232
  if (row === null || typeof row !== "object")
160861
161233
  return false;
@@ -160978,6 +161350,29 @@ function setPersistedDeliveredNoteNudge(db, sessionId, text, messageId = "") {
160978
161350
  function clearPersistedNoteNudge(db, sessionId) {
160979
161351
  db.prepare("UPDATE session_meta SET note_nudge_trigger_pending = 0, note_nudge_trigger_message_id = '', note_nudge_sticky_text = '', note_nudge_sticky_message_id = '' WHERE session_id = ?").run(sessionId);
160980
161352
  }
161353
+ function getPersistedTodoSyntheticAnchor(db, sessionId) {
161354
+ const result = db.prepare("SELECT todo_synthetic_call_id, todo_synthetic_anchor_message_id, todo_synthetic_state_json FROM session_meta WHERE session_id = ?").get(sessionId);
161355
+ if (!isPersistedTodoSyntheticAnchorRow(result)) {
161356
+ return null;
161357
+ }
161358
+ if (result.todo_synthetic_call_id.length === 0 || result.todo_synthetic_anchor_message_id.length === 0) {
161359
+ return null;
161360
+ }
161361
+ return {
161362
+ callId: result.todo_synthetic_call_id,
161363
+ messageId: result.todo_synthetic_anchor_message_id,
161364
+ stateJson: result.todo_synthetic_state_json
161365
+ };
161366
+ }
161367
+ function setPersistedTodoSyntheticAnchor(db, sessionId, callId, messageId, stateJson) {
161368
+ db.transaction(() => {
161369
+ ensureSessionMetaRow(db, sessionId);
161370
+ db.prepare("UPDATE session_meta SET todo_synthetic_call_id = ?, todo_synthetic_anchor_message_id = ?, todo_synthetic_state_json = ? WHERE session_id = ?").run(callId, messageId, stateJson, sessionId);
161371
+ })();
161372
+ }
161373
+ function clearPersistedTodoSyntheticAnchor(db, sessionId) {
161374
+ db.prepare("UPDATE session_meta SET todo_synthetic_call_id = '', todo_synthetic_anchor_message_id = '', todo_synthetic_state_json = '' WHERE session_id = ?").run(sessionId);
161375
+ }
160981
161376
  function getNoteLastReadAt(db, sessionId) {
160982
161377
  try {
160983
161378
  const result = db.prepare("SELECT note_last_read_at FROM session_meta WHERE session_id = ?").get(sessionId);
@@ -161104,6 +161499,39 @@ function removeStrippedPlaceholderId(db, sessionId, messageId) {
161104
161499
  setStrippedPlaceholderIds(db, sessionId, ids);
161105
161500
  return true;
161106
161501
  }
161502
+ function isPendingCompactionMarker(value) {
161503
+ return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
161504
+ }
161505
+ function getPendingCompactionMarkerState(db, sessionId) {
161506
+ const row = db.prepare("SELECT pending_compaction_marker_state FROM session_meta WHERE session_id = ?").get(sessionId);
161507
+ const raw = row?.pending_compaction_marker_state;
161508
+ if (raw === null || raw === undefined || raw === "")
161509
+ return null;
161510
+ try {
161511
+ const parsed = JSON.parse(raw);
161512
+ if (isPendingCompactionMarker(parsed)) {
161513
+ return parsed;
161514
+ }
161515
+ } catch {}
161516
+ return null;
161517
+ }
161518
+ function setPendingCompactionMarkerState(db, sessionId, state) {
161519
+ ensureSessionMetaRow(db, sessionId);
161520
+ const blob = state ? stableStringify(state) : null;
161521
+ db.prepare("UPDATE session_meta SET pending_compaction_marker_state = ? WHERE session_id = ?").run(blob, sessionId);
161522
+ }
161523
+ function clearPendingCompactionMarkerStateIf(db, sessionId, expected) {
161524
+ const expectedBlob = stableStringify(expected);
161525
+ const result = db.prepare(`UPDATE session_meta SET pending_compaction_marker_state = NULL
161526
+ WHERE session_id = ? AND pending_compaction_marker_state = ?`).run(sessionId, expectedBlob);
161527
+ return result.changes > 0;
161528
+ }
161529
+ function getSessionsWithPendingMarker(db) {
161530
+ const rows = db.prepare(`SELECT session_id FROM session_meta
161531
+ WHERE pending_compaction_marker_state IS NOT NULL
161532
+ AND pending_compaction_marker_state != ''`).all();
161533
+ return rows.map((r) => r.session_id);
161534
+ }
161107
161535
  var init_storage_meta_persisted = __esm(() => {
161108
161536
  init_logger();
161109
161537
  init_storage_meta_shared();
@@ -161130,7 +161558,7 @@ var init_resolve_subagent_fallback = __esm(async () => {
161130
161558
 
161131
161559
  // src/features/magic-context/storage-meta-session.ts
161132
161560
  function getOrCreateSessionMeta(db, sessionId) {
161133
- const result = db.prepare("SELECT session_id, last_response_time, cache_ttl, counter, last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent, last_context_percentage, last_input_tokens, times_execute_threshold_reached, compartment_in_progress, system_prompt_hash, system_prompt_tokens, conversation_tokens, tool_call_tokens, cleared_reasoning_through_tag FROM session_meta WHERE session_id = ?").get(sessionId);
161561
+ const result = db.prepare("SELECT session_id, last_response_time, cache_ttl, counter, last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent, last_context_percentage, last_input_tokens, times_execute_threshold_reached, compartment_in_progress, system_prompt_hash, system_prompt_tokens, conversation_tokens, tool_call_tokens, cleared_reasoning_through_tag, last_todo_state FROM session_meta WHERE session_id = ?").get(sessionId);
161134
161562
  if (isSessionMetaRow(result)) {
161135
161563
  return toSessionMeta(result);
161136
161564
  }
@@ -161631,12 +162059,12 @@ var init_storage = __esm(async () => {
161631
162059
  });
161632
162060
 
161633
162061
  // src/shared/models-dev-cache.ts
161634
- import { createHash as createHash3 } from "node:crypto";
162062
+ import { createHash as createHash4 } from "node:crypto";
161635
162063
  import { existsSync as existsSync12, readFileSync as readFileSync8 } from "node:fs";
161636
162064
  import { homedir as homedir8, platform as platform3 } from "node:os";
161637
- import { join as join17 } from "node:path";
162065
+ import { join as join16 } from "node:path";
161638
162066
  function hashFast(input) {
161639
- return createHash3("sha1").update(input).digest("hex");
162067
+ return createHash4("sha1").update(input).digest("hex");
161640
162068
  }
161641
162069
  function getModelsJsonPath() {
161642
162070
  const explicit = process.env.OPENCODE_MODELS_PATH?.trim();
@@ -161645,15 +162073,15 @@ function getModelsJsonPath() {
161645
162073
  const cacheBase = getCacheDir();
161646
162074
  const source = process.env.OPENCODE_MODELS_URL?.trim();
161647
162075
  const filename = source && source !== "https://models.dev" ? `models-${hashFast(source)}.json` : "models.json";
161648
- return join17(cacheBase, "opencode", filename);
162076
+ return join16(cacheBase, "opencode", filename);
161649
162077
  }
161650
162078
  function getOpencodeConfigPath() {
161651
162079
  const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
161652
- const configDir = envDir ? envDir : platform3() === "win32" ? join17(homedir8(), ".config", "opencode") : join17(process.env.XDG_CONFIG_HOME || join17(homedir8(), ".config"), "opencode");
161653
- const jsonc = join17(configDir, "opencode.jsonc");
162080
+ const configDir = envDir ? envDir : platform3() === "win32" ? join16(homedir8(), ".config", "opencode") : join16(process.env.XDG_CONFIG_HOME || join16(homedir8(), ".config"), "opencode");
162081
+ const jsonc = join16(configDir, "opencode.jsonc");
161654
162082
  if (existsSync12(jsonc))
161655
162083
  return jsonc;
161656
- const json2 = join17(configDir, "opencode.json");
162084
+ const json2 = join16(configDir, "opencode.json");
161657
162085
  if (existsSync12(json2))
161658
162086
  return json2;
161659
162087
  return null;
@@ -161704,9 +162132,7 @@ function loadModelsDevMetadataFromFile() {
161704
162132
  try {
161705
162133
  const configPath = getOpencodeConfigPath();
161706
162134
  if (configPath && existsSync12(configPath)) {
161707
- let raw = readFileSync8(configPath, "utf-8");
161708
- raw = raw.replace(/"(?:[^"\\]|\\.)*"|\/\/.*$/gm, (match) => match.startsWith('"') ? match : "");
161709
- const config2 = JSON.parse(raw);
162135
+ const config2 = parseJsonc(readFileSync8(configPath, "utf-8"));
161710
162136
  if (config2.provider && typeof config2.provider === "object") {
161711
162137
  for (const [providerId, provider2] of Object.entries(config2.provider)) {
161712
162138
  if (!provider2?.models || typeof provider2.models !== "object")
@@ -161778,6 +162204,7 @@ function getModelsDevContextLimit(providerID, modelID) {
161778
162204
  var RELOAD_INTERVAL_MS, apiCache = null, apiLoadedAt = 0, recentlySeenApiSizes, oscillationLogged = false, fileCache = null, fileLastAttempt = 0;
161779
162205
  var init_models_dev_cache = __esm(() => {
161780
162206
  init_data_path();
162207
+ init_jsonc_parser();
161781
162208
  init_logger();
161782
162209
  RELOAD_INTERVAL_MS = 5 * 60 * 1000;
161783
162210
  recentlySeenApiSizes = new Set;
@@ -161791,25 +162218,359 @@ __export(exports_rpc_notifications, {
161791
162218
  drainNotifications: () => drainNotifications
161792
162219
  });
161793
162220
  function pushNotification(type, payload, sessionId) {
161794
- queue2.push({ type, payload, sessionId });
162221
+ queue2.push({ id: nextNotificationId++, type, payload, sessionId });
161795
162222
  if (queue2.length > 100) {
161796
162223
  queue2 = queue2.slice(-50);
161797
162224
  }
161798
162225
  }
161799
- function drainNotifications() {
162226
+ function drainNotifications(lastReceivedId = 0) {
161800
162227
  lastDrainAt = Date.now();
161801
- const result = queue2;
161802
- queue2 = [];
161803
- return result;
162228
+ if (lastReceivedId > 0) {
162229
+ queue2 = queue2.filter((notification) => notification.id > lastReceivedId);
162230
+ }
162231
+ return [...queue2];
161804
162232
  }
161805
162233
  function isTuiConnected() {
161806
162234
  return lastDrainAt > 0 && Date.now() - lastDrainAt < TUI_CONNECTED_WINDOW_MS;
161807
162235
  }
161808
- var queue2, lastDrainAt = 0, TUI_CONNECTED_WINDOW_MS = 3000;
162236
+ var queue2, nextNotificationId = 1, lastDrainAt = 0, TUI_CONNECTED_WINDOW_MS = 3000;
161809
162237
  var init_rpc_notifications = __esm(() => {
161810
162238
  queue2 = [];
161811
162239
  });
161812
162240
 
162241
+ // src/features/magic-context/compaction-marker.ts
162242
+ import { join as join17 } from "node:path";
162243
+ function randomBase62(length) {
162244
+ const chars = [];
162245
+ for (let i = 0;i < length; i++) {
162246
+ chars.push(BASE62_CHARS[Math.floor(Math.random() * BASE62_CHARS.length)]);
162247
+ }
162248
+ return chars.join("");
162249
+ }
162250
+ function generateId(prefix, timestampMs, counter = 0n) {
162251
+ const encoded = BigInt(timestampMs) * 0x1000n + counter;
162252
+ const hex3 = encoded.toString(16).padStart(14, "0");
162253
+ return `${prefix}_${hex3}${randomBase62(14)}`;
162254
+ }
162255
+ function generateMessageId(timestampMs, counter = 0n) {
162256
+ return generateId("msg", timestampMs, counter);
162257
+ }
162258
+ function generatePartId(timestampMs, counter = 0n) {
162259
+ return generateId("prt", timestampMs, counter);
162260
+ }
162261
+ function getOpenCodeDbPath3() {
162262
+ return join17(getDataDir(), "opencode", "opencode.db");
162263
+ }
162264
+ function isOpenCodeSchemaCompatible(db, dbPath) {
162265
+ if (cachedSchemaCompatible?.path === dbPath) {
162266
+ return cachedSchemaCompatible.compatible;
162267
+ }
162268
+ try {
162269
+ const messageCols = new Set(db.prepare("PRAGMA table_info(message)").all().map((r) => r.name ?? "").filter((n) => n.length > 0));
162270
+ const partCols = new Set(db.prepare("PRAGMA table_info(part)").all().map((r) => r.name ?? "").filter((n) => n.length > 0));
162271
+ const missingMessage = REQUIRED_MESSAGE_COLUMNS.filter((c) => !messageCols.has(c));
162272
+ const missingPart = REQUIRED_PART_COLUMNS.filter((c) => !partCols.has(c));
162273
+ if (missingMessage.length > 0 || missingPart.length > 0) {
162274
+ log(`[magic-context] compaction-marker: OpenCode DB schema missing required columns ` + `(message: [${missingMessage.join(", ")}], part: [${missingPart.join(", ")}]). ` + `Marker injection disabled for this process. ` + `This usually means OpenCode was updated and magic-context is out of date.`);
162275
+ cachedSchemaCompatible = { path: dbPath, compatible: false };
162276
+ return false;
162277
+ }
162278
+ cachedSchemaCompatible = { path: dbPath, compatible: true };
162279
+ return true;
162280
+ } catch (error51) {
162281
+ log(`[magic-context] compaction-marker: schema probe failed: ${error51 instanceof Error ? error51.message : String(error51)}. ` + `Marker injection disabled until next process restart.`);
162282
+ cachedSchemaCompatible = { path: dbPath, compatible: false };
162283
+ return false;
162284
+ }
162285
+ }
162286
+ function getWritableOpenCodeDb() {
162287
+ const dbPath = getOpenCodeDbPath3();
162288
+ if (cachedWriteDb?.path === dbPath) {
162289
+ return cachedWriteDb.db;
162290
+ }
162291
+ if (cachedWriteDb) {
162292
+ try {
162293
+ closeQuietly(cachedWriteDb.db);
162294
+ } catch {}
162295
+ }
162296
+ const db = new Database(dbPath);
162297
+ db.exec("PRAGMA journal_mode=WAL");
162298
+ db.exec("PRAGMA busy_timeout=5000");
162299
+ cachedWriteDb = { path: dbPath, db };
162300
+ return db;
162301
+ }
162302
+ function findBoundaryUserMessage(sessionId, endOrdinal) {
162303
+ const db = getWritableOpenCodeDb();
162304
+ const rows = db.prepare(`SELECT id, time_created, data
162305
+ FROM message
162306
+ WHERE session_id = ?
162307
+ AND NOT (COALESCE(json_extract(data, '$.summary'), 0) = 1
162308
+ AND COALESCE(json_extract(data, '$.finish'), '') = 'stop')
162309
+ ORDER BY time_created ASC, id ASC
162310
+ LIMIT ?`).all(sessionId, endOrdinal);
162311
+ let bestMatch = null;
162312
+ for (const row of rows) {
162313
+ try {
162314
+ const info = JSON.parse(row.data);
162315
+ if (info.role === "user") {
162316
+ bestMatch = { id: row.id, timeCreated: row.time_created };
162317
+ }
162318
+ } catch {}
162319
+ }
162320
+ return bestMatch;
162321
+ }
162322
+ function getOpenCodeMessageById(sessionId, messageId) {
162323
+ const db = getWritableOpenCodeDb();
162324
+ const row = db.prepare(`SELECT id FROM message WHERE session_id = ? AND id = ? LIMIT 1`).get(sessionId, messageId);
162325
+ return row ?? null;
162326
+ }
162327
+ function injectCompactionMarker(args) {
162328
+ const db = getWritableOpenCodeDb();
162329
+ if (!isOpenCodeSchemaCompatible(db, getOpenCodeDbPath3())) {
162330
+ return null;
162331
+ }
162332
+ const boundary = findBoundaryUserMessage(args.sessionId, args.endOrdinal);
162333
+ if (!boundary) {
162334
+ log(`[magic-context] compaction-marker: no user message found at or before ordinal ${args.endOrdinal}`);
162335
+ return null;
162336
+ }
162337
+ const boundaryTime = boundary.timeCreated;
162338
+ const summaryMsgId = generateMessageId(boundaryTime + 1, 1n);
162339
+ const compactionPartId = generatePartId(boundaryTime, 1n);
162340
+ const summaryPartId = generatePartId(boundaryTime + 1, 2n);
162341
+ const summaryMsgData = JSON.stringify({
162342
+ role: "assistant",
162343
+ parentID: boundary.id,
162344
+ summary: true,
162345
+ finish: "stop",
162346
+ mode: "compaction",
162347
+ agent: "compaction",
162348
+ path: { cwd: args.directory, root: args.directory },
162349
+ cost: 0,
162350
+ tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
162351
+ modelID: "magic-context",
162352
+ providerID: "magic-context",
162353
+ time: { created: boundaryTime + 1 }
162354
+ });
162355
+ try {
162356
+ db.transaction(() => {
162357
+ db.prepare("INSERT INTO part (id, message_id, session_id, time_created, time_updated, data) VALUES (?, ?, ?, ?, ?, ?)").run(compactionPartId, boundary.id, args.sessionId, boundaryTime, boundaryTime, '{"type":"compaction","auto":true}');
162358
+ db.prepare("INSERT INTO message (id, session_id, time_created, time_updated, data) VALUES (?, ?, ?, ?, ?)").run(summaryMsgId, args.sessionId, boundaryTime + 1, boundaryTime + 1, summaryMsgData);
162359
+ db.prepare("INSERT INTO part (id, message_id, session_id, time_created, time_updated, data) VALUES (?, ?, ?, ?, ?, ?)").run(summaryPartId, summaryMsgId, args.sessionId, boundaryTime + 1, boundaryTime + 1, JSON.stringify({ type: "text", text: args.summaryText }));
162360
+ })();
162361
+ log(`[magic-context] compaction-marker: injected boundary at user msg ${boundary.id} (ordinal ~${args.endOrdinal}), summary msg ${summaryMsgId}`);
162362
+ return {
162363
+ boundaryMessageId: boundary.id,
162364
+ summaryMessageId: summaryMsgId,
162365
+ compactionPartId,
162366
+ summaryPartId
162367
+ };
162368
+ } catch (error51) {
162369
+ log(`[magic-context] compaction-marker: injection failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
162370
+ return null;
162371
+ }
162372
+ }
162373
+ function removeCompactionMarker(state) {
162374
+ try {
162375
+ const db = getWritableOpenCodeDb();
162376
+ db.transaction(() => {
162377
+ db.prepare("DELETE FROM part WHERE id = ?").run(state.summaryPartId);
162378
+ db.prepare("DELETE FROM message WHERE id = ?").run(state.summaryMessageId);
162379
+ db.prepare("DELETE FROM part WHERE id = ?").run(state.compactionPartId);
162380
+ })();
162381
+ return true;
162382
+ } catch (error51) {
162383
+ log(`[magic-context] compaction-marker: removal failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
162384
+ return false;
162385
+ }
162386
+ }
162387
+ var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", cachedWriteDb = null, REQUIRED_MESSAGE_COLUMNS, REQUIRED_PART_COLUMNS, cachedSchemaCompatible = null;
162388
+ var init_compaction_marker = __esm(async () => {
162389
+ init_data_path();
162390
+ init_logger();
162391
+ await init_sqlite();
162392
+ REQUIRED_MESSAGE_COLUMNS = ["id", "session_id", "time_created", "time_updated", "data"];
162393
+ REQUIRED_PART_COLUMNS = [
162394
+ "id",
162395
+ "message_id",
162396
+ "session_id",
162397
+ "time_created",
162398
+ "time_updated",
162399
+ "data"
162400
+ ];
162401
+ });
162402
+
162403
+ // src/hooks/magic-context/compaction-marker-manager.ts
162404
+ import { join as join18 } from "node:path";
162405
+ function validatePendingTarget(db, sessionId, pending) {
162406
+ const ocMessage = getOpenCodeMessageById(sessionId, pending.endMessageId);
162407
+ if (!ocMessage) {
162408
+ return "compartment-removed";
162409
+ }
162410
+ const compartments = getCompartmentsByEndMessageId(db, sessionId, pending.endMessageId);
162411
+ if (compartments.length === 0) {
162412
+ return "compartment-removed";
162413
+ }
162414
+ if (compartments.length > 1) {
162415
+ log(`[magic-context][${sessionId}] WARNING: ${compartments.length} compartments share endMessageId=${pending.endMessageId} — schema invariant violated; treating as stale`);
162416
+ return "compartment-removed";
162417
+ }
162418
+ const compartment = compartments[0];
162419
+ if (compartment.endMessage !== pending.ordinal) {
162420
+ return "target-superseded";
162421
+ }
162422
+ return "ok";
162423
+ }
162424
+ function applyDeferredCompactionMarker(db, sessionId, pending, directory) {
162425
+ try {
162426
+ const validation = validatePendingTarget(db, sessionId, pending);
162427
+ if (validation !== "ok") {
162428
+ sessionLog(sessionId, `compaction-marker drain: stale-skip (${validation}) for ordinal ${pending.ordinal} endMessageId=${pending.endMessageId}`);
162429
+ return { kind: "stale-skip", reason: validation };
162430
+ }
162431
+ const existing = getPersistedCompactionMarkerState(db, sessionId);
162432
+ if (existing && existing.boundaryOrdinal >= pending.ordinal) {
162433
+ return { kind: "already-current" };
162434
+ }
162435
+ if (existing) {
162436
+ const removed = removeCompactionMarker(existing);
162437
+ if (!removed) {
162438
+ return {
162439
+ kind: "retryable-failure",
162440
+ error: new Error(`failed to remove old compaction marker at ordinal ${existing.boundaryOrdinal}`)
162441
+ };
162442
+ }
162443
+ sessionLog(sessionId, `compaction-marker drain: removed old boundary at ordinal ${existing.boundaryOrdinal}, advancing to ${pending.ordinal}`);
162444
+ }
162445
+ const result = injectCompactionMarker({
162446
+ sessionId,
162447
+ endOrdinal: pending.ordinal,
162448
+ summaryText: MARKER_SUMMARY_TEXT,
162449
+ directory: directory ?? process.cwd()
162450
+ });
162451
+ if (!result) {
162452
+ return {
162453
+ kind: "retryable-failure",
162454
+ error: new Error(`injectCompactionMarker returned null for ordinal ${pending.ordinal}; will retry`)
162455
+ };
162456
+ }
162457
+ setPersistedCompactionMarkerState(db, sessionId, {
162458
+ ...result,
162459
+ boundaryOrdinal: pending.ordinal
162460
+ });
162461
+ sessionLog(sessionId, `compaction-marker drain: applied at ordinal ${pending.ordinal}, boundary user msg ${result.boundaryMessageId}`);
162462
+ return { kind: "applied", markerOrdinal: pending.ordinal };
162463
+ } catch (err) {
162464
+ const error51 = err instanceof Error ? err : new Error(String(err));
162465
+ sessionLog(sessionId, `compaction-marker drain: retryable failure for ordinal ${pending.ordinal}:`, error51);
162466
+ return { kind: "retryable-failure", error: error51 };
162467
+ }
162468
+ }
162469
+ function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, directory) {
162470
+ const existing = getPersistedCompactionMarkerState(db, sessionId);
162471
+ if (existing) {
162472
+ if (existing.boundaryOrdinal === lastCompartmentEnd) {
162473
+ return;
162474
+ }
162475
+ try {
162476
+ removeCompactionMarker(existing);
162477
+ setPersistedCompactionMarkerState(db, sessionId, null);
162478
+ sessionLog(sessionId, `compaction-marker: removed old boundary at ordinal ${existing.boundaryOrdinal}, moving to ${lastCompartmentEnd}`);
162479
+ } catch (error51) {
162480
+ sessionLog(sessionId, `compaction-marker: failed to remove old boundary at ordinal ${existing.boundaryOrdinal}, proceeding with new injection:`, error51);
162481
+ }
162482
+ }
162483
+ const result = injectCompactionMarker({
162484
+ sessionId,
162485
+ endOrdinal: lastCompartmentEnd,
162486
+ summaryText: MARKER_SUMMARY_TEXT,
162487
+ directory: directory ?? process.cwd()
162488
+ });
162489
+ if (result) {
162490
+ setPersistedCompactionMarkerState(db, sessionId, {
162491
+ ...result,
162492
+ boundaryOrdinal: lastCompartmentEnd
162493
+ });
162494
+ sessionLog(sessionId, `compaction-marker: injected at ordinal ${lastCompartmentEnd}, boundary user msg ${result.boundaryMessageId}`);
162495
+ }
162496
+ }
162497
+ function removeCompactionMarkerForSession(db, sessionId) {
162498
+ const existing = getPersistedCompactionMarkerState(db, sessionId);
162499
+ if (existing) {
162500
+ try {
162501
+ removeCompactionMarker(existing);
162502
+ setPersistedCompactionMarkerState(db, sessionId, null);
162503
+ sessionLog(sessionId, "compaction-marker: removed on session cleanup");
162504
+ } catch (error51) {
162505
+ setPersistedCompactionMarkerState(db, sessionId, null);
162506
+ sessionLog(sessionId, "compaction-marker: removal failed during session cleanup, cleared persisted state:", error51);
162507
+ }
162508
+ }
162509
+ }
162510
+ function checkCompactionMarkerConsistency(db) {
162511
+ const opencodeDbPath = join18(getDataDir(), "opencode", "opencode.db");
162512
+ let opencodeDb;
162513
+ try {
162514
+ opencodeDb = new Database(opencodeDbPath, { readonly: true });
162515
+ } catch (error51) {
162516
+ log(`[magic-context] compaction-marker consistency check skipped: ${error51 instanceof Error ? error51.message : String(error51)}`);
162517
+ return;
162518
+ }
162519
+ try {
162520
+ const persistedRows = db.prepare("SELECT session_id, compaction_marker_state FROM session_meta WHERE compaction_marker_state IS NOT NULL AND compaction_marker_state != ''").all();
162521
+ if (persistedRows.length === 0)
162522
+ return;
162523
+ const checkMessage = opencodeDb.prepare("SELECT 1 FROM message WHERE id = ? LIMIT 1");
162524
+ const checkPart = opencodeDb.prepare("SELECT 1 FROM part WHERE id = ? LIMIT 1");
162525
+ let reconciledCount = 0;
162526
+ for (const row of persistedRows) {
162527
+ const state = getPersistedCompactionMarkerState(db, row.session_id);
162528
+ if (!state)
162529
+ continue;
162530
+ const boundaryExists = checkMessage.get(state.boundaryMessageId) !== null;
162531
+ const summaryMessageExists = checkMessage.get(state.summaryMessageId) !== null;
162532
+ const compactionPartExists = checkPart.get(state.compactionPartId) !== null;
162533
+ const summaryPartExists = checkPart.get(state.summaryPartId) !== null;
162534
+ const allPresent = boundaryExists && summaryMessageExists && compactionPartExists && summaryPartExists;
162535
+ if (allPresent)
162536
+ continue;
162537
+ let removedOk = false;
162538
+ try {
162539
+ removedOk = removeCompactionMarker(state);
162540
+ } catch (error51) {
162541
+ sessionLog(row.session_id, "compaction-marker consistency: partial cleanup of half-written marker failed:", error51);
162542
+ }
162543
+ if (removedOk) {
162544
+ setPersistedCompactionMarkerState(db, row.session_id, null);
162545
+ sessionLog(row.session_id, `compaction-marker consistency: cleared orphaned state (boundary=${boundaryExists} summary=${summaryMessageExists} cPart=${compactionPartExists} sPart=${summaryPartExists}); next publication will re-inject`);
162546
+ reconciledCount++;
162547
+ } else {
162548
+ sessionLog(row.session_id, `compaction-marker consistency: cleanup failed for orphaned state (boundary=${boundaryExists} summary=${summaryMessageExists} cPart=${compactionPartExists} sPart=${summaryPartExists}); will retry on next startup`);
162549
+ }
162550
+ }
162551
+ if (reconciledCount > 0) {
162552
+ log(`[magic-context] compaction-marker consistency: reconciled ${reconciledCount} session(s) with orphaned marker state at startup`);
162553
+ }
162554
+ } catch (error51) {
162555
+ log(`[magic-context] compaction-marker consistency check failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
162556
+ } finally {
162557
+ try {
162558
+ closeQuietly(opencodeDb);
162559
+ } catch {}
162560
+ }
162561
+ }
162562
+ var MARKER_SUMMARY_TEXT = "[Compacted by magic-context — session history is managed by the plugin]";
162563
+ var init_compaction_marker_manager = __esm(async () => {
162564
+ init_compartment_storage();
162565
+ init_storage_meta_persisted();
162566
+ init_data_path();
162567
+ init_logger();
162568
+ await __promiseAll([
162569
+ init_compaction_marker(),
162570
+ init_sqlite()
162571
+ ]);
162572
+ });
162573
+
161813
162574
  // src/hooks/magic-context/compartment-parser.ts
161814
162575
  function parseCompartmentOutput(text) {
161815
162576
  const compartments = [];
@@ -162030,9 +162791,11 @@ var init_compartment_runner_validation = __esm(async () => {
162030
162791
  });
162031
162792
 
162032
162793
  // src/hooks/magic-context/compartment-runner-historian.ts
162033
- import { mkdirSync as mkdirSync5, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
162034
- import { tmpdir as tmpdir2 } from "node:os";
162035
- import { join as join18 } from "node:path";
162794
+ import { mkdirSync as mkdirSync6, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
162795
+ import { join as join19 } from "node:path";
162796
+ function historianResponseDumpDir(directory) {
162797
+ return getProjectMagicContextHistorianDir(directory);
162798
+ }
162036
162799
  async function runValidatedHistorianPass(args) {
162037
162800
  const firstRun = await runHistorianPrompt({
162038
162801
  ...args,
@@ -162125,7 +162888,8 @@ async function runHistorianPrompt(args) {
162125
162888
  timeoutMs,
162126
162889
  dumpLabel,
162127
162890
  modelOverride,
162128
- agentId = HISTORIAN_AGENT
162891
+ agentId = HISTORIAN_AGENT,
162892
+ fallbackModels
162129
162893
  } = args;
162130
162894
  let agentSessionId = null;
162131
162895
  try {
@@ -162152,7 +162916,11 @@ async function runHistorianPrompt(args) {
162152
162916
  ...modelOverride ? { model: modelOverride } : {},
162153
162917
  parts: [{ type: "text", text: prompt, synthetic: true }]
162154
162918
  }
162155
- }, { timeoutMs: timeoutMs ?? DEFAULT_HISTORIAN_TIMEOUT_MS });
162919
+ }, {
162920
+ timeoutMs: timeoutMs ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
162921
+ fallbackModels: modelOverride ? undefined : fallbackModels,
162922
+ callContext: agentId === HISTORIAN_EDITOR_AGENT ? "historian:editor" : "historian"
162923
+ });
162156
162924
  sessionLog(parentSessionId, `historian: prompt completed (attempt ${retryIndex + 1}/${MAX_HISTORIAN_RETRIES + 1})`);
162157
162925
  break;
162158
162926
  } catch (error51) {
@@ -162178,7 +162946,7 @@ async function runHistorianPrompt(args) {
162178
162946
  if (!result) {
162179
162947
  return { ok: false, error: "Historian returned no assistant output." };
162180
162948
  }
162181
- const dumpPath = dumpHistorianResponse(parentSessionId, dumpLabel ?? "historian-response", result);
162949
+ const dumpPath = dumpHistorianResponse(parentSessionId, sessionDirectory, dumpLabel ?? "historian-response", result);
162182
162950
  return { ok: true, result, dumpPath };
162183
162951
  } catch (modelError) {
162184
162952
  const desc = describeError(modelError);
@@ -162270,12 +163038,13 @@ function cleanupHistorianDump(sessionId, dumpPath) {
162270
163038
  });
162271
163039
  }
162272
163040
  }
162273
- function dumpHistorianResponse(sessionId, label, text) {
163041
+ function dumpHistorianResponse(sessionId, directory, label, text) {
162274
163042
  try {
162275
- mkdirSync5(HISTORIAN_RESPONSE_DUMP_DIR, { recursive: true });
163043
+ const dumpDir = historianResponseDumpDir(directory);
163044
+ mkdirSync6(dumpDir, { recursive: true });
162276
163045
  const safeSessionId = sanitizeDumpName(sessionId);
162277
163046
  const safeLabel = sanitizeDumpName(label);
162278
- const dumpPath = join18(HISTORIAN_RESPONSE_DUMP_DIR, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
163047
+ const dumpPath = join19(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
162279
163048
  writeFileSync5(dumpPath, text, "utf8");
162280
163049
  sessionLog(sessionId, "compartment agent: historian response dumped", {
162281
163050
  label,
@@ -162293,14 +163062,14 @@ function dumpHistorianResponse(sessionId, label, text) {
162293
163062
  function sanitizeDumpName(value) {
162294
163063
  return value.replace(/[^a-zA-Z0-9._-]/g, "-");
162295
163064
  }
162296
- var HISTORIAN_RESPONSE_DUMP_DIR, MAX_HISTORIAN_RETRIES = 2;
163065
+ var MAX_HISTORIAN_RETRIES = 2;
162297
163066
  var init_compartment_runner_historian = __esm(async () => {
162298
163067
  init_magic_context();
162299
163068
  init_shared();
162300
163069
  init_assistant_message_extractor();
163070
+ init_data_path();
162301
163071
  init_compartment_prompt();
162302
163072
  await init_compartment_runner_validation();
162303
- HISTORIAN_RESPONSE_DUMP_DIR = join18(tmpdir2(), "magic-context-historian");
162304
163073
  });
162305
163074
 
162306
163075
  // src/hooks/magic-context/compartment-runner-state-xml.ts
@@ -162602,16 +163371,24 @@ function trimMemoriesToBudget(sessionId, memories, budgetTokens) {
162602
163371
  function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, projectPath, injectionBudgetTokens, temporalAwareness) {
162603
163372
  const cached2 = injectionCache.get(sessionId);
162604
163373
  if (!isCacheBusting && cached2) {
162605
- if (cached2.compartmentEndMessageId.length > 0) {
162606
- const cutoffIndex2 = messages.findIndex((message) => message.info.id === cached2.compartmentEndMessageId);
162607
- if (cutoffIndex2 >= 0) {
162608
- const remaining = messages.slice(cutoffIndex2 + 1);
162609
- messages.splice(0, messages.length, ...remaining);
162610
- } else {
162611
- sessionLog(sessionId, `compartment injection: cached boundary ${cached2.compartmentEndMessageId} not in messages (already trimmed), reusing cache`);
163374
+ if (cached2.kind === "empty") {
163375
+ return null;
163376
+ }
163377
+ const prepared = cached2.injection;
163378
+ if (prepared.compartmentEndMessageId === null) {
163379
+ sessionLog(sessionId, "compartment injection cache in degraded mode (null boundary), forcing rebuild");
163380
+ } else {
163381
+ if (prepared.compartmentEndMessageId.length > 0) {
163382
+ const cutoffIndex2 = messages.findIndex((message) => message.info.id === prepared.compartmentEndMessageId);
163383
+ if (cutoffIndex2 >= 0) {
163384
+ const remaining = messages.slice(cutoffIndex2 + 1);
163385
+ messages.splice(0, messages.length, ...remaining);
163386
+ } else {
163387
+ sessionLog(sessionId, `compartment injection: cached boundary ${prepared.compartmentEndMessageId} not in messages (already trimmed), reusing cache`);
163388
+ }
162612
163389
  }
163390
+ return { ...prepared, rebuiltFromDb: false };
162613
163391
  }
162614
- return cached2;
162615
163392
  }
162616
163393
  const compartments = getCompartments(db, sessionId);
162617
163394
  const facts = getSessionFacts(db, sessionId);
@@ -162643,7 +163420,11 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
162643
163420
  }
162644
163421
  }
162645
163422
  if (compartments.length === 0 && facts.length === 0 && !memoryBlock) {
162646
- injectionCache.delete(sessionId);
163423
+ injectionCache.set(sessionId, {
163424
+ kind: "empty",
163425
+ compartmentEndMessageId: "",
163426
+ renderedBytes: 0
163427
+ });
162647
163428
  return null;
162648
163429
  }
162649
163430
  let dateRanges;
@@ -162676,9 +163457,10 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
162676
163457
  compartmentCount: 0,
162677
163458
  skippedVisibleMessages: 0,
162678
163459
  factCount: facts.length,
162679
- memoryCount
163460
+ memoryCount,
163461
+ rebuiltFromDb: true
162680
163462
  };
162681
- injectionCache.set(sessionId, result2);
163463
+ injectionCache.set(sessionId, { kind: "populated", injection: result2 });
162682
163464
  return result2;
162683
163465
  }
162684
163466
  const lastCompartment = compartments[compartments.length - 1];
@@ -162696,9 +163478,10 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
162696
163478
  compartmentCount: compartments.length,
162697
163479
  skippedVisibleMessages: 0,
162698
163480
  factCount: facts.length,
162699
- memoryCount
163481
+ memoryCount,
163482
+ rebuiltFromDb: true
162700
163483
  };
162701
- injectionCache.set(sessionId, result2);
163484
+ injectionCache.set(sessionId, { kind: "populated", injection: result2 });
162702
163485
  return result2;
162703
163486
  }
162704
163487
  let skippedVisibleMessages = 0;
@@ -162707,17 +163490,20 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
162707
163490
  skippedVisibleMessages = cutoffIndex + 1;
162708
163491
  const remaining = messages.slice(cutoffIndex + 1);
162709
163492
  messages.splice(0, messages.length, ...remaining);
163493
+ } else {
163494
+ sessionLog(sessionId, `compartment injection entering degraded mode: boundary ${lastEndMessageId} not in visible messages`);
162710
163495
  }
162711
163496
  const result = {
162712
163497
  block,
162713
163498
  compartmentEndMessage: lastEnd,
162714
- compartmentEndMessageId: lastEndMessageId,
163499
+ compartmentEndMessageId: cutoffIndex >= 0 ? lastEndMessageId : null,
162715
163500
  compartmentCount: compartments.length,
162716
163501
  skippedVisibleMessages,
162717
163502
  factCount: facts.length,
162718
- memoryCount
163503
+ memoryCount,
163504
+ rebuiltFromDb: true
162719
163505
  };
162720
- injectionCache.set(sessionId, result);
163506
+ injectionCache.set(sessionId, { kind: "populated", injection: result });
162721
163507
  return result;
162722
163508
  }
162723
163509
  function renderCompartmentInjection(sessionId, messages, prepared) {
@@ -163009,9 +163795,18 @@ async function executePartialRecompInternal(deps, range) {
163009
163795
  }
163010
163796
  setRecompPartialRange(db, sessionId, null);
163011
163797
  clearCompressionDepthRange(db, sessionId, snapStart, snapEnd);
163012
- clearInjectionCache(sessionId);
163013
- deps.onInjectionCacheCleared?.(sessionId);
163798
+ if (deps.preserveInjectionCacheUntilConsumed !== true) {
163799
+ clearInjectionCache(sessionId);
163800
+ }
163801
+ deps.onCompartmentStatePublished?.(sessionId);
163014
163802
  const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
163803
+ if (deps.experimentalCompactionMarkers && lastEnd > 0) {
163804
+ updateCompactionMarkerAfterPublication(db, sessionId, lastEnd, deps.directory);
163805
+ const stalePending = getPendingCompactionMarkerState(db, sessionId);
163806
+ if (stalePending) {
163807
+ clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
163808
+ }
163809
+ }
163015
163810
  return { compartmentCount: merged.length, lastEndMessage: lastEnd };
163016
163811
  };
163017
163812
  const protectedTailStart = getProtectedTailStartOrdinal(sessionId);
@@ -163117,6 +163912,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
163117
163912
  dumpLabelBase: `partial-recomp-${sessionId}-${chunk.startIndex}-${chunk.endIndex}-pass-${passCount + 1}`,
163118
163913
  timeoutMs: historianTimeoutMs,
163119
163914
  fallbackModelId: deps.fallbackModelId,
163915
+ fallbackModels: deps.fallbackModels,
163120
163916
  twoPass: deps.historianTwoPass,
163121
163917
  callbacks: {
163122
163918
  onRepairRetry: async (error51) => {
@@ -163210,6 +164006,7 @@ var init_compartment_runner_partial_recomp = __esm(async () => {
163210
164006
  init_send_session_notification();
163211
164007
  await __promiseAll([
163212
164008
  init_storage_meta(),
164009
+ init_compaction_marker_manager(),
163213
164010
  init_compartment_runner_historian(),
163214
164011
  init_compartment_runner_validation(),
163215
164012
  init_inject_compartments(),
@@ -163278,269 +164075,6 @@ var init_derive_budgets = __esm(() => {
163278
164075
  init_models_dev_cache();
163279
164076
  });
163280
164077
 
163281
- // src/features/magic-context/compaction-marker.ts
163282
- import { join as join19 } from "node:path";
163283
- function randomBase62(length) {
163284
- const chars = [];
163285
- for (let i = 0;i < length; i++) {
163286
- chars.push(BASE62_CHARS[Math.floor(Math.random() * BASE62_CHARS.length)]);
163287
- }
163288
- return chars.join("");
163289
- }
163290
- function generateId(prefix, timestampMs, counter = 0n) {
163291
- const encoded = BigInt(timestampMs) * 0x1000n + counter;
163292
- const hex3 = encoded.toString(16).padStart(14, "0");
163293
- return `${prefix}_${hex3}${randomBase62(14)}`;
163294
- }
163295
- function generateMessageId(timestampMs, counter = 0n) {
163296
- return generateId("msg", timestampMs, counter);
163297
- }
163298
- function generatePartId(timestampMs, counter = 0n) {
163299
- return generateId("prt", timestampMs, counter);
163300
- }
163301
- function getOpenCodeDbPath3() {
163302
- return join19(getDataDir(), "opencode", "opencode.db");
163303
- }
163304
- function isOpenCodeSchemaCompatible(db, dbPath) {
163305
- if (cachedSchemaCompatible?.path === dbPath) {
163306
- return cachedSchemaCompatible.compatible;
163307
- }
163308
- try {
163309
- const messageCols = new Set(db.prepare("PRAGMA table_info(message)").all().map((r) => r.name ?? "").filter((n) => n.length > 0));
163310
- const partCols = new Set(db.prepare("PRAGMA table_info(part)").all().map((r) => r.name ?? "").filter((n) => n.length > 0));
163311
- const missingMessage = REQUIRED_MESSAGE_COLUMNS.filter((c) => !messageCols.has(c));
163312
- const missingPart = REQUIRED_PART_COLUMNS.filter((c) => !partCols.has(c));
163313
- if (missingMessage.length > 0 || missingPart.length > 0) {
163314
- log(`[magic-context] compaction-marker: OpenCode DB schema missing required columns ` + `(message: [${missingMessage.join(", ")}], part: [${missingPart.join(", ")}]). ` + `Marker injection disabled for this process. ` + `This usually means OpenCode was updated and magic-context is out of date.`);
163315
- cachedSchemaCompatible = { path: dbPath, compatible: false };
163316
- return false;
163317
- }
163318
- cachedSchemaCompatible = { path: dbPath, compatible: true };
163319
- return true;
163320
- } catch (error51) {
163321
- log(`[magic-context] compaction-marker: schema probe failed: ${error51 instanceof Error ? error51.message : String(error51)}. ` + `Marker injection disabled until next process restart.`);
163322
- cachedSchemaCompatible = { path: dbPath, compatible: false };
163323
- return false;
163324
- }
163325
- }
163326
- function getWritableOpenCodeDb() {
163327
- const dbPath = getOpenCodeDbPath3();
163328
- if (cachedWriteDb?.path === dbPath) {
163329
- return cachedWriteDb.db;
163330
- }
163331
- if (cachedWriteDb) {
163332
- try {
163333
- closeQuietly(cachedWriteDb.db);
163334
- } catch {}
163335
- }
163336
- const db = new Database(dbPath);
163337
- db.exec("PRAGMA journal_mode=WAL");
163338
- db.exec("PRAGMA busy_timeout=5000");
163339
- cachedWriteDb = { path: dbPath, db };
163340
- return db;
163341
- }
163342
- function findBoundaryUserMessage(sessionId, endOrdinal) {
163343
- const db = getWritableOpenCodeDb();
163344
- const rows = db.prepare(`SELECT id, time_created, data
163345
- FROM message
163346
- WHERE session_id = ?
163347
- AND NOT (COALESCE(json_extract(data, '$.summary'), 0) = 1
163348
- AND COALESCE(json_extract(data, '$.finish'), '') = 'stop')
163349
- ORDER BY time_created ASC, id ASC
163350
- LIMIT ?`).all(sessionId, endOrdinal);
163351
- let bestMatch = null;
163352
- for (const row of rows) {
163353
- try {
163354
- const info = JSON.parse(row.data);
163355
- if (info.role === "user") {
163356
- bestMatch = { id: row.id, timeCreated: row.time_created };
163357
- }
163358
- } catch {}
163359
- }
163360
- return bestMatch;
163361
- }
163362
- function injectCompactionMarker(args) {
163363
- const db = getWritableOpenCodeDb();
163364
- if (!isOpenCodeSchemaCompatible(db, getOpenCodeDbPath3())) {
163365
- return null;
163366
- }
163367
- const boundary = findBoundaryUserMessage(args.sessionId, args.endOrdinal);
163368
- if (!boundary) {
163369
- log(`[magic-context] compaction-marker: no user message found at or before ordinal ${args.endOrdinal}`);
163370
- return null;
163371
- }
163372
- const boundaryTime = boundary.timeCreated;
163373
- const summaryMsgId = generateMessageId(boundaryTime + 1, 1n);
163374
- const compactionPartId = generatePartId(boundaryTime, 1n);
163375
- const summaryPartId = generatePartId(boundaryTime + 1, 2n);
163376
- const summaryMsgData = JSON.stringify({
163377
- role: "assistant",
163378
- parentID: boundary.id,
163379
- summary: true,
163380
- finish: "stop",
163381
- mode: "compaction",
163382
- agent: "compaction",
163383
- path: { cwd: args.directory, root: args.directory },
163384
- cost: 0,
163385
- tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
163386
- modelID: "magic-context",
163387
- providerID: "magic-context",
163388
- time: { created: boundaryTime + 1 }
163389
- });
163390
- try {
163391
- db.transaction(() => {
163392
- db.prepare("INSERT INTO part (id, message_id, session_id, time_created, time_updated, data) VALUES (?, ?, ?, ?, ?, ?)").run(compactionPartId, boundary.id, args.sessionId, boundaryTime, boundaryTime, '{"type":"compaction","auto":true}');
163393
- db.prepare("INSERT INTO message (id, session_id, time_created, time_updated, data) VALUES (?, ?, ?, ?, ?)").run(summaryMsgId, args.sessionId, boundaryTime + 1, boundaryTime + 1, summaryMsgData);
163394
- db.prepare("INSERT INTO part (id, message_id, session_id, time_created, time_updated, data) VALUES (?, ?, ?, ?, ?, ?)").run(summaryPartId, summaryMsgId, args.sessionId, boundaryTime + 1, boundaryTime + 1, JSON.stringify({ type: "text", text: args.summaryText }));
163395
- })();
163396
- log(`[magic-context] compaction-marker: injected boundary at user msg ${boundary.id} (ordinal ~${args.endOrdinal}), summary msg ${summaryMsgId}`);
163397
- return {
163398
- boundaryMessageId: boundary.id,
163399
- summaryMessageId: summaryMsgId,
163400
- compactionPartId,
163401
- summaryPartId
163402
- };
163403
- } catch (error51) {
163404
- log(`[magic-context] compaction-marker: injection failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
163405
- return null;
163406
- }
163407
- }
163408
- function removeCompactionMarker(state) {
163409
- try {
163410
- const db = getWritableOpenCodeDb();
163411
- db.transaction(() => {
163412
- db.prepare("DELETE FROM part WHERE id = ?").run(state.summaryPartId);
163413
- db.prepare("DELETE FROM message WHERE id = ?").run(state.summaryMessageId);
163414
- db.prepare("DELETE FROM part WHERE id = ?").run(state.compactionPartId);
163415
- })();
163416
- return true;
163417
- } catch (error51) {
163418
- log(`[magic-context] compaction-marker: removal failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
163419
- return false;
163420
- }
163421
- }
163422
- var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", cachedWriteDb = null, REQUIRED_MESSAGE_COLUMNS, REQUIRED_PART_COLUMNS, cachedSchemaCompatible = null;
163423
- var init_compaction_marker = __esm(async () => {
163424
- init_data_path();
163425
- init_logger();
163426
- await init_sqlite();
163427
- REQUIRED_MESSAGE_COLUMNS = ["id", "session_id", "time_created", "time_updated", "data"];
163428
- REQUIRED_PART_COLUMNS = [
163429
- "id",
163430
- "message_id",
163431
- "session_id",
163432
- "time_created",
163433
- "time_updated",
163434
- "data"
163435
- ];
163436
- });
163437
-
163438
- // src/hooks/magic-context/compaction-marker-manager.ts
163439
- import { join as join20 } from "node:path";
163440
- function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, directory) {
163441
- const existing = getPersistedCompactionMarkerState(db, sessionId);
163442
- if (existing) {
163443
- if (existing.boundaryOrdinal === lastCompartmentEnd) {
163444
- return;
163445
- }
163446
- try {
163447
- removeCompactionMarker(existing);
163448
- setPersistedCompactionMarkerState(db, sessionId, null);
163449
- sessionLog(sessionId, `compaction-marker: removed old boundary at ordinal ${existing.boundaryOrdinal}, moving to ${lastCompartmentEnd}`);
163450
- } catch (error51) {
163451
- sessionLog(sessionId, `compaction-marker: failed to remove old boundary at ordinal ${existing.boundaryOrdinal}, proceeding with new injection:`, error51);
163452
- }
163453
- }
163454
- const result = injectCompactionMarker({
163455
- sessionId,
163456
- endOrdinal: lastCompartmentEnd,
163457
- summaryText: MARKER_SUMMARY_TEXT,
163458
- directory: directory ?? process.cwd()
163459
- });
163460
- if (result) {
163461
- setPersistedCompactionMarkerState(db, sessionId, {
163462
- ...result,
163463
- boundaryOrdinal: lastCompartmentEnd
163464
- });
163465
- sessionLog(sessionId, `compaction-marker: injected at ordinal ${lastCompartmentEnd}, boundary user msg ${result.boundaryMessageId}`);
163466
- }
163467
- }
163468
- function removeCompactionMarkerForSession(db, sessionId) {
163469
- const existing = getPersistedCompactionMarkerState(db, sessionId);
163470
- if (existing) {
163471
- try {
163472
- removeCompactionMarker(existing);
163473
- setPersistedCompactionMarkerState(db, sessionId, null);
163474
- sessionLog(sessionId, "compaction-marker: removed on session cleanup");
163475
- } catch (error51) {
163476
- setPersistedCompactionMarkerState(db, sessionId, null);
163477
- sessionLog(sessionId, "compaction-marker: removal failed during session cleanup, cleared persisted state:", error51);
163478
- }
163479
- }
163480
- }
163481
- function checkCompactionMarkerConsistency(db) {
163482
- const opencodeDbPath = join20(getDataDir(), "opencode", "opencode.db");
163483
- let opencodeDb;
163484
- try {
163485
- opencodeDb = new Database(opencodeDbPath, { readonly: true });
163486
- } catch (error51) {
163487
- log(`[magic-context] compaction-marker consistency check skipped: ${error51 instanceof Error ? error51.message : String(error51)}`);
163488
- return;
163489
- }
163490
- try {
163491
- const persistedRows = db.prepare("SELECT session_id, compaction_marker_state FROM session_meta WHERE compaction_marker_state IS NOT NULL AND compaction_marker_state != ''").all();
163492
- if (persistedRows.length === 0)
163493
- return;
163494
- const checkMessage = opencodeDb.prepare("SELECT 1 FROM message WHERE id = ? LIMIT 1");
163495
- const checkPart = opencodeDb.prepare("SELECT 1 FROM part WHERE id = ? LIMIT 1");
163496
- let reconciledCount = 0;
163497
- for (const row of persistedRows) {
163498
- const state = getPersistedCompactionMarkerState(db, row.session_id);
163499
- if (!state)
163500
- continue;
163501
- const boundaryExists = checkMessage.get(state.boundaryMessageId) !== null;
163502
- const summaryMessageExists = checkMessage.get(state.summaryMessageId) !== null;
163503
- const compactionPartExists = checkPart.get(state.compactionPartId) !== null;
163504
- const summaryPartExists = checkPart.get(state.summaryPartId) !== null;
163505
- const allPresent = boundaryExists && summaryMessageExists && compactionPartExists && summaryPartExists;
163506
- if (allPresent)
163507
- continue;
163508
- let removedOk = false;
163509
- try {
163510
- removedOk = removeCompactionMarker(state);
163511
- } catch (error51) {
163512
- sessionLog(row.session_id, "compaction-marker consistency: partial cleanup of half-written marker failed:", error51);
163513
- }
163514
- if (removedOk) {
163515
- setPersistedCompactionMarkerState(db, row.session_id, null);
163516
- sessionLog(row.session_id, `compaction-marker consistency: cleared orphaned state (boundary=${boundaryExists} summary=${summaryMessageExists} cPart=${compactionPartExists} sPart=${summaryPartExists}); next publication will re-inject`);
163517
- reconciledCount++;
163518
- } else {
163519
- sessionLog(row.session_id, `compaction-marker consistency: cleanup failed for orphaned state (boundary=${boundaryExists} summary=${summaryMessageExists} cPart=${compactionPartExists} sPart=${summaryPartExists}); will retry on next startup`);
163520
- }
163521
- }
163522
- if (reconciledCount > 0) {
163523
- log(`[magic-context] compaction-marker consistency: reconciled ${reconciledCount} session(s) with orphaned marker state at startup`);
163524
- }
163525
- } catch (error51) {
163526
- log(`[magic-context] compaction-marker consistency check failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
163527
- } finally {
163528
- try {
163529
- closeQuietly(opencodeDb);
163530
- } catch {}
163531
- }
163532
- }
163533
- var MARKER_SUMMARY_TEXT = "[Compacted by magic-context — session history is managed by the plugin]";
163534
- var init_compaction_marker_manager = __esm(async () => {
163535
- init_storage_meta_persisted();
163536
- init_data_path();
163537
- init_logger();
163538
- await __promiseAll([
163539
- init_compaction_marker(),
163540
- init_sqlite()
163541
- ]);
163542
- });
163543
-
163544
164078
  // src/hooks/magic-context/note-nudger.ts
163545
164079
  function getPersistedNoteNudgeDeliveredAt(_db, sessionId) {
163546
164080
  return lastDeliveredAt.get(sessionId) ?? 0;
@@ -163866,15 +164400,15 @@ var init_caveman = __esm(() => {
163866
164400
  });
163867
164401
 
163868
164402
  // src/hooks/magic-context/historian-state-file.ts
163869
- import { mkdirSync as mkdirSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "node:fs";
163870
- import { tmpdir as tmpdir3 } from "node:os";
163871
- import { join as join21 } from "node:path";
163872
- function maybeWriteHistorianStateFile(sessionId, existingState) {
164403
+ import { mkdirSync as mkdirSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "node:fs";
164404
+ import { join as join20 } from "node:path";
164405
+ function maybeWriteHistorianStateFile(sessionId, existingState, directory) {
163873
164406
  if (existingState.length <= HISTORIAN_STATE_INLINE_THRESHOLD)
163874
164407
  return;
163875
164408
  try {
163876
- mkdirSync6(HISTORIAN_STATE_DIR, { recursive: true });
163877
- const path5 = join21(HISTORIAN_STATE_DIR, `state-${sessionId}-${Date.now()}.xml`);
164409
+ const dir = getProjectMagicContextHistorianDir(directory);
164410
+ mkdirSync7(dir, { recursive: true });
164411
+ const path5 = join20(dir, `state-${sessionId}-${Date.now()}.xml`);
163878
164412
  writeFileSync6(path5, existingState, "utf8");
163879
164413
  return path5;
163880
164414
  } catch {
@@ -163888,9 +164422,9 @@ function cleanupHistorianStateFile(path5) {
163888
164422
  unlinkSync2(path5);
163889
164423
  } catch {}
163890
164424
  }
163891
- var HISTORIAN_STATE_INLINE_THRESHOLD = 30000, HISTORIAN_STATE_DIR;
164425
+ var HISTORIAN_STATE_INLINE_THRESHOLD = 30000;
163892
164426
  var init_historian_state_file = __esm(() => {
163893
- HISTORIAN_STATE_DIR = join21(tmpdir3(), "magic-context-historian");
164427
+ init_data_path();
163894
164428
  });
163895
164429
 
163896
164430
  // src/features/magic-context/memory/embedding-backfill.ts
@@ -164318,7 +164852,8 @@ async function runCompressorPass(args) {
164318
164852
  targetTokens,
164319
164853
  outputCount,
164320
164854
  outputDepth,
164321
- historianTimeoutMs
164855
+ historianTimeoutMs,
164856
+ fallbackModels
164322
164857
  } = args;
164323
164858
  const prompt = buildCompressorPrompt(compartments, currentTokens, targetTokens, outputDepth, outputCount);
164324
164859
  let agentSessionId = null;
@@ -164340,7 +164875,11 @@ async function runCompressorPass(args) {
164340
164875
  agent: HISTORIAN_AGENT2,
164341
164876
  parts: [{ type: "text", text: prompt, synthetic: true }]
164342
164877
  }
164343
- }, { timeoutMs: historianTimeoutMs ?? DEFAULT_HISTORIAN_TIMEOUT_MS });
164878
+ }, {
164879
+ timeoutMs: historianTimeoutMs ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
164880
+ fallbackModels,
164881
+ callContext: "compressor"
164882
+ });
164344
164883
  const messagesResponse = await client.session.messages({
164345
164884
  path: { id: agentSessionId },
164346
164885
  query: { directory }
@@ -164497,7 +165036,7 @@ No new compartments or facts were written.`);
164497
165036
  const existingState = priorCompartments.length > 0 || priorFacts.length > 0 ? buildExistingStateXml(priorCompartments, priorFacts, memoryBlock) : memoryBlock ? `${memoryBlock}
164498
165037
 
164499
165038
  This is your first run. No existing compartments or facts.` : "This is your first run. No existing state.";
164500
- stateFilePath = maybeWriteHistorianStateFile(sessionId, existingState);
165039
+ stateFilePath = maybeWriteHistorianStateFile(sessionId, existingState, directory);
164501
165040
  if (stateFilePath) {
164502
165041
  sessionLog(sessionId, `historian: existing state offloaded to file (${existingState.length} chars) → ${stateFilePath}`);
164503
165042
  }
@@ -164520,6 +165059,7 @@ ${chunk.text}`, { stateFilePath });
164520
165059
  dumpLabelBase: `incremental-${sessionId}-${chunk.startIndex}-${chunk.endIndex}`,
164521
165060
  timeoutMs: historianTimeoutMs,
164522
165061
  fallbackModelId: deps.fallbackModelId,
165062
+ fallbackModels: deps.fallbackModels,
164523
165063
  twoPass: deps.historianTwoPass
164524
165064
  });
164525
165065
  if (!validatedPass.ok) {
@@ -164544,21 +165084,36 @@ Historian returned compartments that made no forward progress beyond raw message
164544
165084
  No new compartments or facts were written. Check the historian model/output and try again.`);
164545
165085
  return;
164546
165086
  }
165087
+ const deferMarkerApplication = deps.preserveInjectionCacheUntilConsumed === true && deps.experimentalCompactionMarkers === true;
165088
+ const lastCompartmentEnd = lastNewEnd;
165089
+ const lastNewEndMessageId = newCompartments[newCompartments.length - 1]?.endMessageId;
164547
165090
  db.transaction(() => {
164548
165091
  appendCompartments(db, sessionId, newCompartments);
164549
165092
  replaceSessionFacts(db, sessionId, validatedPass.facts ?? []);
164550
165093
  clearHistorianFailureState(db, sessionId);
164551
165094
  clearEmergencyRecovery(db, sessionId);
165095
+ if (deferMarkerApplication && lastNewEndMessageId) {
165096
+ setPendingCompactionMarkerState(db, sessionId, {
165097
+ ordinal: lastCompartmentEnd,
165098
+ endMessageId: lastNewEndMessageId,
165099
+ publishedAt: Date.now()
165100
+ });
165101
+ }
164552
165102
  })();
164553
- clearInjectionCache(sessionId);
164554
- deps.onInjectionCacheCleared?.(sessionId);
165103
+ if (deps.preserveInjectionCacheUntilConsumed !== true) {
165104
+ clearInjectionCache(sessionId);
165105
+ }
165106
+ deps.onCompartmentStatePublished?.(sessionId);
164555
165107
  if (deps.directory && deps.memoryEnabled !== false && deps.autoPromote !== false) {
164556
165108
  promoteSessionFactsToMemory(db, sessionId, resolveProjectIdentity(deps.directory), validatedPass.facts ?? []);
164557
165109
  }
164558
- const lastCompartmentEnd = lastNewEnd;
164559
165110
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd);
164560
165111
  if (deps.experimentalCompactionMarkers) {
164561
- updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, sessionDirectory);
165112
+ if (deferMarkerApplication) {
165113
+ deps.onDeferredMarkerPending?.(sessionId);
165114
+ } else {
165115
+ updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, sessionDirectory);
165116
+ }
164562
165117
  }
164563
165118
  if (deps.historyBudgetTokens && deps.historyBudgetTokens > 0) {
164564
165119
  await runCompressionPassIfNeeded({
@@ -164568,6 +165123,7 @@ No new compartments or facts were written. Check the historian model/output and
164568
165123
  directory: sessionDirectory,
164569
165124
  historyBudgetTokens: deps.historyBudgetTokens,
164570
165125
  historianTimeoutMs,
165126
+ fallbackModels: deps.fallbackModels,
164571
165127
  minCompartmentRatio: deps.compressorMinCompartmentRatio,
164572
165128
  maxMergeDepth: deps.compressorMaxMergeDepth
164573
165129
  });
@@ -164662,8 +165218,10 @@ async function executeContextRecompInternal(deps) {
164662
165218
  if (!promoted2)
164663
165219
  return null;
164664
165220
  clearCompressionDepth(db, sessionId);
164665
- clearInjectionCache(sessionId);
164666
- deps.onInjectionCacheCleared?.(sessionId);
165221
+ if (deps.preserveInjectionCacheUntilConsumed !== true) {
165222
+ clearInjectionCache(sessionId);
165223
+ }
165224
+ deps.onCompartmentStatePublished?.(sessionId);
164667
165225
  if (deps.directory && deps.memoryEnabled !== false && deps.autoPromote !== false) {
164668
165226
  promoteSessionFactsToMemory(db, sessionId, resolveProjectIdentity(deps.directory), promoted2.facts);
164669
165227
  }
@@ -164673,6 +165231,10 @@ async function executeContextRecompInternal(deps) {
164673
165231
  }
164674
165232
  if (deps.experimentalCompactionMarkers && lastCompartmentEnd2 > 0) {
164675
165233
  updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd2, deps.directory);
165234
+ const stalePending = getPendingCompactionMarkerState(db, sessionId);
165235
+ if (stalePending) {
165236
+ clearPendingCompactionMarkerStateIf(db, sessionId, stalePending);
165237
+ }
164676
165238
  }
164677
165239
  return [
164678
165240
  `Persisted ${promoted2.compartments.length} compartment${promoted2.compartments.length === 1 ? "" : "s"} from ${passCount} successful pass${passCount === 1 ? "" : "es"}.`,
@@ -164738,7 +165300,7 @@ Nothing was written.`;
164738
165300
 
164739
165301
  This is your first run. No existing compartments or facts.` : "This is your first run. No existing state.";
164740
165302
  cleanupHistorianStateFile(currentStateFilePath);
164741
- currentStateFilePath = maybeWriteHistorianStateFile(sessionId, existingState);
165303
+ currentStateFilePath = maybeWriteHistorianStateFile(sessionId, existingState, sessionDirectory);
164742
165304
  const prompt = buildCompartmentAgentPrompt(existingState, `Messages ${chunk.startIndex}-${chunk.endIndex}:
164743
165305
 
164744
165306
  ${chunk.text}`, { stateFilePath: currentStateFilePath });
@@ -164756,6 +165318,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
164756
165318
  dumpLabelBase: `recomp-${sessionId}-${chunk.startIndex}-${chunk.endIndex}-pass-${passCount + 1}`,
164757
165319
  timeoutMs: historianTimeoutMs,
164758
165320
  fallbackModelId: deps.fallbackModelId,
165321
+ fallbackModels: deps.fallbackModels,
164759
165322
  twoPass: deps.historianTwoPass,
164760
165323
  callbacks: {
164761
165324
  onRepairRetry: async (error51) => {
@@ -164833,8 +165396,10 @@ Nothing was written.`;
164833
165396
  clearRecompStaging(db, sessionId);
164834
165397
  }
164835
165398
  clearCompressionDepth(db, sessionId);
164836
- clearInjectionCache(sessionId);
164837
- deps.onInjectionCacheCleared?.(sessionId);
165399
+ if (deps.preserveInjectionCacheUntilConsumed !== true) {
165400
+ clearInjectionCache(sessionId);
165401
+ }
165402
+ deps.onCompartmentStatePublished?.(sessionId);
164838
165403
  const finalCompartments = promoted?.compartments ?? candidateCompartments;
164839
165404
  const finalFacts = promoted?.facts ?? candidateFacts;
164840
165405
  if (deps.directory && deps.memoryEnabled !== false && deps.autoPromote !== false) {
@@ -164853,6 +165418,7 @@ Nothing was written.`;
164853
165418
  directory: sessionDirectory,
164854
165419
  historyBudgetTokens: deps.historyBudgetTokens,
164855
165420
  historianTimeoutMs,
165421
+ fallbackModels: deps.fallbackModels,
164856
165422
  minCompartmentRatio: deps.compressorMinCompartmentRatio,
164857
165423
  maxMergeDepth: deps.compressorMaxMergeDepth
164858
165424
  });
@@ -164908,50 +165474,78 @@ __export(exports_compartment_runner, {
164908
165474
  startCompartmentAgent: () => startCompartmentAgent,
164909
165475
  runCompartmentAgent: () => runCompartmentAgent,
164910
165476
  registerActiveCompartmentRun: () => registerActiveCompartmentRun,
165477
+ markActiveCompartmentRunPublished: () => markActiveCompartmentRunPublished,
164911
165478
  getActiveCompartmentRun: () => getActiveCompartmentRun,
164912
165479
  executeContextRecomp: () => executeContextRecomp
164913
165480
  });
164914
165481
  function getActiveCompartmentRun(sessionId) {
164915
165482
  return activeRuns.get(sessionId);
164916
165483
  }
165484
+ function markActiveCompartmentRunPublished(sessionId) {
165485
+ const activeRun = activeRuns.get(sessionId);
165486
+ if (activeRun)
165487
+ activeRun.published = true;
165488
+ }
164917
165489
  function registerActiveCompartmentRun(sessionId, promise2) {
165490
+ const activeRun = {
165491
+ promise: Promise.resolve(),
165492
+ published: false
165493
+ };
164918
165494
  const wrapped = promise2.finally(() => {
164919
- if (activeRuns.get(sessionId) === wrapped) {
165495
+ if (activeRuns.get(sessionId)?.promise === wrapped) {
164920
165496
  activeRuns.delete(sessionId);
164921
165497
  }
164922
165498
  });
164923
- activeRuns.set(sessionId, wrapped);
165499
+ activeRun.promise = wrapped;
165500
+ activeRuns.set(sessionId, activeRun);
165501
+ return activeRun;
165502
+ }
165503
+ function withPublishedCallback(deps) {
165504
+ return {
165505
+ ...deps,
165506
+ onCompartmentStatePublished: (sid) => {
165507
+ markActiveCompartmentRunPublished(sid);
165508
+ deps.onCompartmentStatePublished?.(sid);
165509
+ }
165510
+ };
164924
165511
  }
164925
165512
  function startCompartmentAgent(deps) {
164926
165513
  const existing = activeRuns.get(deps.sessionId);
164927
165514
  if (existing) {
164928
165515
  return;
164929
165516
  }
164930
- const promise2 = runCompartmentAgent(deps).catch((err) => {
165517
+ const runnerDeps = withPublishedCallback(deps);
165518
+ const promise2 = runCompartmentAgent(runnerDeps).catch((err) => {
164931
165519
  sessionLog(deps.sessionId, "compartment agent: unhandled rejection:", err);
164932
165520
  try {
164933
165521
  updateSessionMeta(deps.db, deps.sessionId, { compartmentInProgress: false });
164934
165522
  } catch {}
164935
165523
  }).finally(() => {
164936
- activeRuns.delete(deps.sessionId);
165524
+ if (activeRuns.get(deps.sessionId)?.promise === promise2) {
165525
+ activeRuns.delete(deps.sessionId);
165526
+ }
164937
165527
  });
164938
- activeRuns.set(deps.sessionId, promise2);
165528
+ activeRuns.set(deps.sessionId, { promise: promise2, published: false });
164939
165529
  }
164940
165530
  async function executeContextRecomp(deps, options = {}) {
164941
165531
  const { sessionId } = deps;
164942
165532
  if (activeRuns.has(sessionId)) {
164943
165533
  return "## Magic Recomp\n\nHistorian is already running for this session. Wait for it to finish, then try `/ctx-recomp` again.";
164944
165534
  }
164945
- const promise2 = options.range ? executePartialRecompInternal(deps, options.range) : executeContextRecompInternal(deps);
164946
- activeRuns.set(sessionId, promise2.then(() => {
165535
+ const runnerDeps = withPublishedCallback(deps);
165536
+ const promise2 = options.range ? executePartialRecompInternal(runnerDeps, options.range) : executeContextRecompInternal(runnerDeps);
165537
+ const wrappedPromise = promise2.then(() => {
164947
165538
  return;
164948
165539
  }).catch((err) => {
164949
165540
  sessionLog(sessionId, "compartment agent: recomp unhandled rejection:", err);
164950
- }));
165541
+ });
165542
+ activeRuns.set(sessionId, { promise: wrappedPromise, published: false });
164951
165543
  try {
164952
165544
  return await promise2;
164953
165545
  } finally {
164954
- activeRuns.delete(sessionId);
165546
+ if (activeRuns.get(sessionId)?.promise === wrappedPromise) {
165547
+ activeRuns.delete(sessionId);
165548
+ }
164955
165549
  }
164956
165550
  }
164957
165551
  var activeRuns;
@@ -164972,8 +165566,8 @@ var exports_tui_config = {};
164972
165566
  __export(exports_tui_config, {
164973
165567
  ensureTuiPluginEntry: () => ensureTuiPluginEntry
164974
165568
  });
164975
- import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "node:fs";
164976
- import { dirname as dirname7, join as join24 } from "node:path";
165569
+ import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "node:fs";
165570
+ import { dirname as dirname8, join as join23 } from "node:path";
164977
165571
  function isMagicContextEntry(entry) {
164978
165572
  if (!entry)
164979
165573
  return false;
@@ -164987,8 +165581,8 @@ function isMagicContextEntry(entry) {
164987
165581
  }
164988
165582
  function resolveTuiConfigPath() {
164989
165583
  const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
164990
- const jsoncPath = join24(configDir, "tui.jsonc");
164991
- const jsonPath = join24(configDir, "tui.json");
165584
+ const jsoncPath = join23(configDir, "tui.jsonc");
165585
+ const jsonPath = join23(configDir, "tui.json");
164992
165586
  if (existsSync14(jsoncPath))
164993
165587
  return jsoncPath;
164994
165588
  if (existsSync14(jsonPath))
@@ -165000,7 +165594,7 @@ function ensureTuiPluginEntry() {
165000
165594
  const configPath = resolveTuiConfigPath();
165001
165595
  let config2 = {};
165002
165596
  if (existsSync14(configPath)) {
165003
- const raw = readFileSync10(configPath, "utf-8");
165597
+ const raw = readFileSync11(configPath, "utf-8");
165004
165598
  config2 = import_comment_json3.parse(raw) ?? {};
165005
165599
  }
165006
165600
  const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((p) => typeof p === "string") : [];
@@ -165019,7 +165613,7 @@ function ensureTuiPluginEntry() {
165019
165613
  plugins.push(PLUGIN_ENTRY);
165020
165614
  }
165021
165615
  config2.plugin = plugins;
165022
- mkdirSync8(dirname7(configPath), { recursive: true });
165616
+ mkdirSync9(dirname8(configPath), { recursive: true });
165023
165617
  writeFileSync8(configPath, `${import_comment_json3.stringify(config2, null, 2)}
165024
165618
  `);
165025
165619
  log(`[magic-context] updated TUI plugin entry in ${configPath}`);
@@ -165130,34 +165724,20 @@ function loadConfigFile(configPath) {
165130
165724
  return null;
165131
165725
  }
165132
165726
  }
165133
- function mergeConfigs(base, override, options = {}) {
165134
- const config2 = {
165135
- ...base,
165136
- ...override,
165137
- auto_update: options.allowUserOnlyFields ? override.auto_update : base.auto_update,
165138
- memory: {
165139
- ...base.memory ?? {},
165140
- ...override.memory ?? {}
165141
- },
165142
- embedding: override.embedding ?? base.embedding,
165143
- historian: override.historian ?? base.historian,
165144
- dreamer: override.dreamer ? {
165145
- ...base.dreamer ?? {},
165146
- ...override.dreamer
165147
- } : base.dreamer,
165148
- sidekick: override.sidekick ? {
165149
- ...base.sidekick ?? {},
165150
- ...override.sidekick
165151
- } : base.sidekick,
165152
- disabled_hooks: [
165153
- ...new Set([...base.disabled_hooks ?? [], ...override.disabled_hooks ?? []])
165154
- ],
165155
- command: {
165156
- ...base.command ?? {},
165157
- ...override.command ?? {}
165727
+ function deepMergeRawConfig(base, override) {
165728
+ const result = { ...base };
165729
+ for (const key of Object.keys(override)) {
165730
+ const baseVal = base[key];
165731
+ const overrideVal = override[key];
165732
+ if (baseVal !== null && typeof baseVal === "object" && !Array.isArray(baseVal) && overrideVal !== null && typeof overrideVal === "object" && !Array.isArray(overrideVal)) {
165733
+ result[key] = deepMergeRawConfig(baseVal, overrideVal);
165734
+ } else if (key === "disabled_hooks" && Array.isArray(baseVal) && Array.isArray(overrideVal)) {
165735
+ result[key] = [...new Set([...baseVal, ...overrideVal])];
165736
+ } else {
165737
+ result[key] = overrideVal;
165158
165738
  }
165159
- };
165160
- return config2;
165739
+ }
165740
+ return result;
165161
165741
  }
165162
165742
  function getProjectUserOnlyFields(config2) {
165163
165743
  return "auto_update" in config2 ? ["auto_update"] : [];
@@ -165298,30 +165878,38 @@ function loadPluginConfig(directory) {
165298
165878
  const projectDetected = rootDetected.format !== "none" ? rootDetected : dotOpenCodeDetected;
165299
165879
  const userLoaded = userDetected.format === "none" ? null : loadConfigFile(userDetected.path);
165300
165880
  const projectLoaded = projectDetected.format === "none" ? null : loadConfigFile(projectDetected.path);
165301
- let config2 = parsePluginConfig({});
165302
165881
  const allWarnings = [];
165882
+ let mergedRaw = {};
165303
165883
  if (userLoaded) {
165304
165884
  allWarnings.push(...userLoaded.warnings.map((w) => `[user config] ${w}`));
165305
- const parsed = parsePluginConfig(userLoaded.config);
165306
- if (parsed.configWarnings?.length) {
165307
- allWarnings.push(...parsed.configWarnings.map((w) => `[user config] ${w}`));
165308
- }
165309
- config2 = mergeConfigs(config2, parsed, { allowUserOnlyFields: true });
165885
+ mergedRaw = deepMergeRawConfig(mergedRaw, userLoaded.config);
165310
165886
  }
165311
165887
  if (projectLoaded) {
165312
165888
  allWarnings.push(...projectLoaded.warnings.map((w) => `[project config] ${w}`));
165313
- const parsed = parsePluginConfig(projectLoaded.config);
165314
- if (parsed.configWarnings?.length) {
165315
- allWarnings.push(...parsed.configWarnings.map((w) => `[project config] ${w}`));
165316
- }
165317
- const strippedUserOnlyFields = getProjectUserOnlyFields(projectLoaded.config);
165889
+ const projectRaw = { ...projectLoaded.config };
165890
+ const strippedUserOnlyFields = getProjectUserOnlyFields(projectRaw);
165318
165891
  if (strippedUserOnlyFields.length > 0) {
165892
+ for (const key of strippedUserOnlyFields) {
165893
+ delete projectRaw[key];
165894
+ }
165319
165895
  allWarnings.push(`[project config] Ignoring ${strippedUserOnlyFields.join(", ")} from project config (security: these settings only honor user-level config)`);
165320
165896
  }
165321
- config2 = mergeConfigs(config2, parsed);
165897
+ mergedRaw = deepMergeRawConfig(mergedRaw, projectRaw);
165898
+ }
165899
+ const config2 = parsePluginConfig(mergedRaw);
165900
+ if (config2.configWarnings?.length) {
165901
+ allWarnings.push(...config2.configWarnings.map((w) => {
165902
+ if (userLoaded && projectLoaded)
165903
+ return `[config] ${w}`;
165904
+ if (userLoaded)
165905
+ return `[user config] ${w}`;
165906
+ return `[project config] ${w}`;
165907
+ }));
165322
165908
  }
165323
165909
  if (allWarnings.length > 0) {
165324
165910
  config2.configWarnings = allWarnings;
165911
+ } else if ("configWarnings" in config2) {
165912
+ config2.configWarnings = undefined;
165325
165913
  }
165326
165914
  return config2;
165327
165915
  }
@@ -165722,6 +166310,7 @@ function buildDreamTaskPrompt(task, args) {
165722
166310
  init_shared();
165723
166311
  init_assistant_message_extractor();
165724
166312
  init_logger();
166313
+ init_resolve_fallbacks();
165725
166314
 
165726
166315
  // src/features/magic-context/sidekick/core.ts
165727
166316
  var SIDEKICK_SYSTEM_PROMPT = `You are Sidekick, a focused memory-retrieval subagent for an AI coding assistant.
@@ -165741,6 +166330,7 @@ function stripThinkingBlocks(text) {
165741
166330
 
165742
166331
  // src/features/magic-context/sidekick/agent.ts
165743
166332
  async function runSidekick(deps) {
166333
+ const fallbackModels = resolveFallbackChain(SIDEKICK_AGENT, deps.config.fallback_models);
165744
166334
  let agentSessionId = null;
165745
166335
  try {
165746
166336
  const createResponse = await deps.client.session.create({
@@ -165763,7 +166353,7 @@ async function runSidekick(deps) {
165763
166353
  system: deps.config.system_prompt?.trim() || deps.config.prompt?.trim() || SIDEKICK_SYSTEM_PROMPT,
165764
166354
  parts: [{ type: "text", text: deps.userMessage, synthetic: true }]
165765
166355
  }
165766
- }, { timeoutMs: deps.config.timeout_ms });
166356
+ }, { timeoutMs: deps.config.timeout_ms, fallbackModels, callContext: "sidekick" });
165767
166357
  const messagesResponse = await deps.client.session.messages({
165768
166358
  path: { id: agentSessionId },
165769
166359
  query: { directory: deps.sessionDirectory ?? deps.projectPath }
@@ -165801,41 +166391,41 @@ init_tool_definition_tokens();
165801
166391
 
165802
166392
  // src/hooks/auto-update-checker/index.ts
165803
166393
  init_logger();
165804
- import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync6, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
165805
- import { dirname as dirname4, join as join6 } from "node:path";
166394
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync6, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
166395
+ import { dirname as dirname5, join as join6 } from "node:path";
165806
166396
 
165807
166397
  // src/hooks/auto-update-checker/cache.ts
165808
166398
  init_logger();
165809
166399
  var import_comment_json2 = __toESM(require_src2(), 1);
165810
166400
  import { spawn } from "node:child_process";
165811
166401
  import { existsSync as existsSync5, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
165812
- import { basename, dirname as dirname3, join as join5 } from "node:path";
166402
+ import { basename, dirname as dirname4, join as join5 } from "node:path";
165813
166403
 
165814
166404
  // src/hooks/auto-update-checker/checker.ts
165815
166405
  init_logger();
165816
166406
  var import_comment_json = __toESM(require_src2(), 1);
165817
166407
  import { existsSync as existsSync4, readFileSync as readFileSync4, statSync, writeFileSync } from "node:fs";
165818
- import { homedir as homedir4 } from "node:os";
165819
- import { dirname as dirname2, isAbsolute as isAbsolute2, join as join4, resolve as resolve2 } from "node:path";
166408
+ import { homedir as homedir5 } from "node:os";
166409
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, resolve as resolve2 } from "node:path";
165820
166410
  import { fileURLToPath } from "node:url";
165821
166411
 
165822
166412
  // src/hooks/auto-update-checker/constants.ts
165823
- import { homedir as homedir3, platform } from "node:os";
166413
+ import { homedir as homedir4, platform } from "node:os";
165824
166414
  import { join as join3 } from "node:path";
165825
166415
  var PACKAGE_NAME = "@cortexkit/opencode-magic-context";
165826
166416
  var NPM_REGISTRY_URL = "https://registry.npmjs.org";
165827
166417
  var NPM_FETCH_TIMEOUT = 1e4;
165828
166418
  function getOpenCodeCacheRoot() {
165829
166419
  if (platform() === "win32") {
165830
- return join3(process.env.LOCALAPPDATA ?? homedir3(), "opencode");
166420
+ return join3(process.env.LOCALAPPDATA ?? homedir4(), "opencode");
165831
166421
  }
165832
- return join3(homedir3(), ".cache", "opencode");
166422
+ return join3(homedir4(), ".cache", "opencode");
165833
166423
  }
165834
166424
  function getOpenCodeConfigRoot() {
165835
166425
  if (platform() === "win32") {
165836
- return join3(process.env.APPDATA ?? join3(homedir3(), "AppData", "Roaming"), "opencode");
166426
+ return join3(process.env.APPDATA ?? join3(homedir4(), "AppData", "Roaming"), "opencode");
165837
166427
  }
165838
- return join3(process.env.XDG_CONFIG_HOME ?? join3(homedir3(), ".config"), "opencode");
166428
+ return join3(process.env.XDG_CONFIG_HOME ?? join3(homedir4(), ".config"), "opencode");
165839
166429
  }
165840
166430
  var CACHE_DIR = join3(getOpenCodeCacheRoot(), "packages");
165841
166431
  var USER_OPENCODE_CONFIG = join3(getOpenCodeConfigRoot(), "opencode.json");
@@ -165917,12 +166507,12 @@ function resolvePathPluginSpec(spec, configPath) {
165917
166507
  }
165918
166508
  if (isAbsolute2(spec) || /^[A-Za-z]:[\\/]/.test(spec))
165919
166509
  return spec;
165920
- return resolve2(dirname2(configPath), spec);
166510
+ return resolve2(dirname3(configPath), spec);
165921
166511
  }
165922
166512
  function findPackageJsonUp(startPath) {
165923
166513
  try {
165924
166514
  const stat = statSync(startPath);
165925
- let dir = stat.isDirectory() ? startPath : dirname2(startPath);
166515
+ let dir = stat.isDirectory() ? startPath : dirname3(startPath);
165926
166516
  for (let i = 0;i < 10; i++) {
165927
166517
  const pkgPath = join4(dir, "package.json");
165928
166518
  if (existsSync4(pkgPath)) {
@@ -165932,7 +166522,7 @@ function findPackageJsonUp(startPath) {
165932
166522
  return pkgPath;
165933
166523
  } catch {}
165934
166524
  }
165935
- const parent = dirname2(dir);
166525
+ const parent = dirname3(dir);
165936
166526
  if (parent === dir)
165937
166527
  break;
165938
166528
  dir = parent;
@@ -165980,7 +166570,7 @@ function getLocalDevVersion(directory) {
165980
166570
  }
165981
166571
  function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
165982
166572
  try {
165983
- return findPackageJsonUp(dirname2(fileURLToPath(currentModuleUrl)));
166573
+ return findPackageJsonUp(dirname3(fileURLToPath(currentModuleUrl)));
165984
166574
  } catch (err) {
165985
166575
  warn(`[auto-update-checker] Failed to resolve runtime package path: ${String(err)}`);
165986
166576
  return null;
@@ -166023,7 +166613,7 @@ function getCachedVersion(spec) {
166023
166613
  getCurrentRuntimePackageJsonPath(),
166024
166614
  spec ? getSpecCachePackageJsonPath(spec) : null,
166025
166615
  getSpecCachePackageJsonPath(`${PACKAGE_NAME}@latest`),
166026
- join4(homedir4(), ".cache", "opencode", "node_modules", PACKAGE_NAME, "package.json")
166616
+ join4(homedir5(), ".cache", "opencode", "node_modules", PACKAGE_NAME, "package.json")
166027
166617
  ].filter(isString);
166028
166618
  for (const packageJsonPath of candidates) {
166029
166619
  try {
@@ -166077,7 +166667,7 @@ function stripPackageNameFromPath(pathValue, packageName) {
166077
166667
  for (const segment of [...packageName.split("/")].reverse()) {
166078
166668
  if (basename(current) !== segment)
166079
166669
  return null;
166080
- current = dirname3(current);
166670
+ current = dirname4(current);
166081
166671
  }
166082
166672
  return current;
166083
166673
  }
@@ -166140,19 +166730,19 @@ function removeInstalledPackage(installDir, packageName) {
166140
166730
  }
166141
166731
  function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
166142
166732
  if (runtimePackageJsonPath) {
166143
- const packageDir = dirname3(runtimePackageJsonPath);
166733
+ const packageDir = dirname4(runtimePackageJsonPath);
166144
166734
  const nodeModulesDir = stripPackageNameFromPath(packageDir, PACKAGE_NAME);
166145
166735
  if (nodeModulesDir && basename(nodeModulesDir) === "node_modules") {
166146
- const installDir = dirname3(nodeModulesDir);
166736
+ const installDir = dirname4(nodeModulesDir);
166147
166737
  const packageJsonPath = join5(installDir, "package.json");
166148
166738
  if (existsSync5(packageJsonPath))
166149
166739
  return { installDir, packageJsonPath };
166150
166740
  }
166151
166741
  return null;
166152
166742
  }
166153
- const legacyPackageJsonPath = join5(dirname3(CACHE_DIR), "package.json");
166743
+ const legacyPackageJsonPath = join5(dirname4(CACHE_DIR), "package.json");
166154
166744
  if (existsSync5(legacyPackageJsonPath)) {
166155
- return { installDir: dirname3(CACHE_DIR), packageJsonPath: legacyPackageJsonPath };
166745
+ return { installDir: dirname4(CACHE_DIR), packageJsonPath: legacyPackageJsonPath };
166156
166746
  }
166157
166747
  return null;
166158
166748
  }
@@ -166281,7 +166871,7 @@ function claimCheckSlot(storageDir, intervalMs) {
166281
166871
  }
166282
166872
  } catch {}
166283
166873
  }
166284
- mkdirSync(dirname4(file2), { recursive: true });
166874
+ mkdirSync2(dirname5(file2), { recursive: true });
166285
166875
  const tmp = `${file2}.tmp.${process.pid}`;
166286
166876
  writeFileSync3(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
166287
166877
  renameSync(tmp, file2);
@@ -166379,15 +166969,16 @@ function createLiveSessionState() {
166379
166969
  variantBySession: new Map,
166380
166970
  agentBySession: new Map,
166381
166971
  historyRefreshSessions: new Set,
166972
+ deferredHistoryRefreshSessions: new Set,
166382
166973
  systemPromptRefreshSessions: new Set,
166383
166974
  pendingMaterializationSessions: new Set,
166975
+ deferredMaterializationSessions: new Set,
166384
166976
  sessionDirectoryBySession: new Map
166385
166977
  };
166386
166978
  }
166387
166979
 
166388
166980
  // src/index.ts
166389
166981
  init_conflict_warning_hook();
166390
-
166391
166982
  // src/features/magic-context/dreamer/storage-dream-state.ts
166392
166983
  var getDreamStateStatements = new WeakMap;
166393
166984
  var setDreamStateStatements = new WeakMap;
@@ -166484,11 +167075,23 @@ function releaseLease(db, holderId) {
166484
167075
  })();
166485
167076
  }
166486
167077
  // src/features/magic-context/dreamer/queue.ts
167078
+ function hasActiveDreamLease(db) {
167079
+ try {
167080
+ return isLeaseActive(db);
167081
+ } catch (error51) {
167082
+ if (String(error51).includes("no such table: dream_state")) {
167083
+ return false;
167084
+ }
167085
+ throw error51;
167086
+ }
167087
+ }
166487
167088
  function enqueueDream(db, projectIdentity, reason, force = false) {
166488
167089
  const now = Date.now();
166489
167090
  return db.transaction(() => {
166490
- const staleThresholdMs = force ? 2 * 60 * 1000 : 120 * 60 * 1000;
166491
- db.prepare("DELETE FROM dream_queue WHERE project_path = ? AND started_at IS NOT NULL AND started_at < ?").run([projectIdentity, now - staleThresholdMs]);
167091
+ if (!hasActiveDreamLease(db)) {
167092
+ const staleThresholdMs = force ? 2 * 60 * 1000 : 120 * 60 * 1000;
167093
+ db.prepare("DELETE FROM dream_queue WHERE project_path = ? AND started_at IS NOT NULL AND started_at < ?").run([projectIdentity, now - staleThresholdMs]);
167094
+ }
166492
167095
  const existing = db.prepare("SELECT id FROM dream_queue WHERE project_path = ?").get(projectIdentity);
166493
167096
  if (existing) {
166494
167097
  return null;
@@ -166549,7 +167152,7 @@ init_data_path();
166549
167152
  init_logger();
166550
167153
  await init_sqlite();
166551
167154
  import { existsSync as existsSync9 } from "node:fs";
166552
- import { join as join13 } from "node:path";
167155
+ import { join as join12 } from "node:path";
166553
167156
 
166554
167157
  // src/features/magic-context/key-files/identify-key-files.ts
166555
167158
  init_logger();
@@ -166833,7 +167436,12 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
166833
167436
  system: DREAMER_SYSTEM_PROMPT,
166834
167437
  parts: [{ type: "text", text: prompt, synthetic: true }]
166835
167438
  }
166836
- }, { timeoutMs: Math.min(remainingMs, 5 * 60 * 1000), signal: abortController.signal });
167439
+ }, {
167440
+ timeoutMs: Math.min(remainingMs, 5 * 60 * 1000),
167441
+ signal: abortController.signal,
167442
+ fallbackModels: args.fallbackModels,
167443
+ callContext: "dreamer:user-memories"
167444
+ });
166837
167445
  const messagesResponse = await args.client.session.messages({
166838
167446
  path: { id: agentSessionId },
166839
167447
  query: { directory: args.sessionDirectory }
@@ -166892,7 +167500,8 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
166892
167500
  }
166893
167501
  return result;
166894
167502
  } catch (error51) {
166895
- log(`[dreamer] user-memories: review failed: ${getErrorMessage(error51)}`);
167503
+ const errorDescription = describeError(error51);
167504
+ log(`[dreamer] user-memories: review failed: ${errorDescription.brief}`, errorDescription.stackHead ? { stackHead: errorDescription.stackHead } : undefined);
166896
167505
  return result;
166897
167506
  } finally {
166898
167507
  clearInterval(leaseInterval);
@@ -166923,6 +167532,7 @@ function insertDreamRun(db, run) {
166923
167532
 
166924
167533
  // src/features/magic-context/dreamer/runner.ts
166925
167534
  var dreamProjectDirectories = new Map;
167535
+ var CIRCUIT_BREAKER_THRESHOLD = 3;
166926
167536
  function registerDreamProjectDirectory(projectIdentity, directory) {
166927
167537
  dreamProjectDirectories.set(projectIdentity, directory);
166928
167538
  }
@@ -166939,8 +167549,27 @@ function countNewIds(beforeIds, afterIds) {
166939
167549
  }
166940
167550
  return count;
166941
167551
  }
167552
+ function getCircuitBreakerSignature(error51, brief) {
167553
+ if (error51 instanceof Error && error51.name && error51.name !== "Error") {
167554
+ return error51.name;
167555
+ }
167556
+ const namedError = error51;
167557
+ if (namedError && typeof namedError === "object" && typeof namedError.name === "string" && namedError.name.length > 0 && namedError.name !== "Error") {
167558
+ return namedError.name;
167559
+ }
167560
+ return brief.split(":")[0]?.trim().split(/\s+/)[0] || brief || "unknown";
167561
+ }
167562
+ function shouldSkipCircuitBreaker(error51, brief) {
167563
+ const namedError = error51;
167564
+ const name2 = error51 instanceof Error ? error51.name : namedError && typeof namedError === "object" && typeof namedError.name === "string" ? namedError.name : "";
167565
+ const combined = `${name2} ${brief}`.toLowerCase();
167566
+ return name2 === "AbortError" || combined.includes("lease");
167567
+ }
167568
+ function logWithStackHead(message, stackHead) {
167569
+ log(message, stackHead ? { stackHead } : undefined);
167570
+ }
166942
167571
  function getOpenCodeDbPath2() {
166943
- return join13(getDataDir(), "opencode", "opencode.db");
167572
+ return join12(getDataDir(), "opencode", "opencode.db");
166944
167573
  }
166945
167574
  function openOpenCodeDb() {
166946
167575
  const dbPath = getOpenCodeDbPath2();
@@ -167015,9 +167644,11 @@ async function identifyKeyFilesForSession(args) {
167015
167644
  try {
167016
167645
  if (!renewLease(args.db, args.holderId)) {
167017
167646
  log(`[key-files][${args.sessionId}] lease renewal failed — aborting`);
167647
+ args.onLeaseLost?.(`key-files:${args.sessionId}`);
167018
167648
  abortController.abort();
167019
167649
  }
167020
- } catch {
167650
+ } catch (error51) {
167651
+ args.onLeaseLost?.(`key-files:${args.sessionId}`, error51);
167021
167652
  abortController.abort();
167022
167653
  }
167023
167654
  }, 60000);
@@ -167044,7 +167675,12 @@ async function identifyKeyFilesForSession(args) {
167044
167675
  system: KEY_FILES_SYSTEM_PROMPT,
167045
167676
  parts: [{ type: "text", text: prompt, synthetic: true }]
167046
167677
  }
167047
- }, { timeoutMs: Math.min(remainingMs, 300000), signal: abortController.signal });
167678
+ }, {
167679
+ timeoutMs: Math.min(remainingMs, 300000),
167680
+ signal: abortController.signal,
167681
+ fallbackModels: args.fallbackModels,
167682
+ callContext: `dreamer:key-files:${args.sessionId.slice(0, 12)}`
167683
+ });
167048
167684
  const messagesResponse = await args.client.session.messages({
167049
167685
  path: { id: agentSessionId },
167050
167686
  query: { directory: args.sessionDirectory }
@@ -167067,6 +167703,10 @@ async function identifyKeyFilesForSession(args) {
167067
167703
  log(`[key-files][${args.sessionId}] could not parse agent output — using heuristic fallback`);
167068
167704
  applyHeuristicFallback();
167069
167705
  } catch (error51) {
167706
+ if (args.isLeaseLost?.() || abortController.signal.aborted) {
167707
+ log(`[key-files][${args.sessionId}] lease lost during identification — skipping heuristic fallback`);
167708
+ throw error51;
167709
+ }
167070
167710
  log(`[key-files][${args.sessionId}] identification failed: ${getErrorMessage(error51)} — using heuristic fallback`);
167071
167711
  try {
167072
167712
  applyHeuristicFallback();
@@ -167107,6 +167747,10 @@ async function identifyKeyFiles(args) {
167107
167747
  }
167108
167748
  log(`[key-files] evaluating ${sessionIds.length} active session(s) for ${args.projectIdentity}`);
167109
167749
  for (const sessionId of sessionIds) {
167750
+ if (args.isLeaseLost?.()) {
167751
+ log("[key-files] lease lost — stopping key-file identification");
167752
+ break;
167753
+ }
167110
167754
  if (Date.now() > args.deadline) {
167111
167755
  log("[key-files] deadline reached — stopping key-file identification");
167112
167756
  break;
@@ -167117,6 +167761,9 @@ async function identifyKeyFiles(args) {
167117
167761
  parentSessionId: args.parentSessionId,
167118
167762
  sessionDirectory: args.sessionDirectory,
167119
167763
  holderId: args.holderId,
167764
+ fallbackModels: args.fallbackModels,
167765
+ onLeaseLost: args.onLeaseLost,
167766
+ isLeaseLost: args.isLeaseLost,
167120
167767
  deadline: args.deadline,
167121
167768
  sessionId,
167122
167769
  config: args.config
@@ -167170,8 +167817,53 @@ async function runDream(args) {
167170
167817
  const deadline = startedAt + args.maxRuntimeMinutes * 60 * 1000;
167171
167818
  const lastDreamAt = getDreamState(args.db, `last_dream_at:${args.projectIdentity}`) ?? getDreamState(args.db, "last_dream_at");
167172
167819
  log(`[dreamer] last dream at: ${lastDreamAt ?? "never"} (project=${args.projectIdentity})`);
167820
+ let lastErrorSignature = null;
167821
+ let consecutiveSameErrorFailures = 0;
167822
+ let circuitBreakerTripped = false;
167823
+ let lostLease = false;
167824
+ let lostLeaseReason = null;
167825
+ let lostLeaseRecorded = false;
167826
+ const markLeaseLost = (phase, error51) => {
167827
+ const detail = error51 ? `: ${getErrorMessage(error51)}` : "";
167828
+ lostLeaseReason = `Dream lease lost during ${phase}${detail}`;
167829
+ if (!lostLease) {
167830
+ log(`[dreamer] FATAL: ${lostLeaseReason}; aborting all remaining dream work`);
167831
+ } else {
167832
+ log(`[dreamer] FATAL: ${lostLeaseReason}; dream work is already aborting`);
167833
+ }
167834
+ lostLease = true;
167835
+ };
167836
+ const recordLeaseLostTask = (phase) => {
167837
+ if (lostLeaseRecorded)
167838
+ return;
167839
+ lostLeaseRecorded = true;
167840
+ result.tasks.push({
167841
+ name: "lease-lost",
167842
+ durationMs: 0,
167843
+ result: "",
167844
+ error: lostLeaseReason ?? `Dream lease lost during ${phase}; aborted remaining work`
167845
+ });
167846
+ };
167847
+ const verifyLeaseStillHeld = (phase) => {
167848
+ if (lostLease)
167849
+ return false;
167850
+ try {
167851
+ if (!renewLease(args.db, holderId)) {
167852
+ markLeaseLost(phase);
167853
+ return false;
167854
+ }
167855
+ return true;
167856
+ } catch (error51) {
167857
+ markLeaseLost(phase, error51);
167858
+ return false;
167859
+ }
167860
+ };
167173
167861
  try {
167174
167862
  for (const taskName of args.tasks) {
167863
+ if (!verifyLeaseStillHeld(`before task ${taskName}`)) {
167864
+ recordLeaseLostTask(`before task ${taskName}`);
167865
+ break;
167866
+ }
167175
167867
  if (Date.now() > deadline) {
167176
167868
  log(`[dreamer] deadline reached, stopping after ${result.tasks.length} tasks`);
167177
167869
  break;
@@ -167184,18 +167876,20 @@ async function runDream(args) {
167184
167876
  try {
167185
167877
  if (!renewLease(args.db, holderId)) {
167186
167878
  log(`[dreamer] task ${taskName}: lease renewal failed — aborting LLM call`);
167879
+ markLeaseLost(`task ${taskName} lease renewal`);
167187
167880
  taskAbortController.abort();
167188
167881
  }
167189
167882
  } catch (err) {
167190
167883
  log(`[dreamer] task ${taskName}: lease renewal threw — aborting LLM call: ${err}`);
167884
+ markLeaseLost(`task ${taskName} lease renewal`, err);
167191
167885
  taskAbortController.abort();
167192
167886
  }
167193
167887
  }, 60000);
167194
167888
  try {
167195
167889
  const docsDir = args.sessionDirectory ?? args.projectIdentity;
167196
167890
  const existingDocs = taskName === "maintain-docs" ? {
167197
- architecture: existsSync9(join13(docsDir, "ARCHITECTURE.md")),
167198
- structure: existsSync9(join13(docsDir, "STRUCTURE.md"))
167891
+ architecture: existsSync9(join12(docsDir, "ARCHITECTURE.md")),
167892
+ structure: existsSync9(join12(docsDir, "STRUCTURE.md"))
167199
167893
  } : undefined;
167200
167894
  const userMemories = taskName === "archive-stale" ? getActiveUserMemories(args.db).map((um) => ({
167201
167895
  id: um.id,
@@ -167230,8 +167924,13 @@ async function runDream(args) {
167230
167924
  }
167231
167925
  }, {
167232
167926
  timeoutMs: args.taskTimeoutMinutes * 60 * 1000,
167233
- signal: taskAbortController.signal
167927
+ signal: taskAbortController.signal,
167928
+ fallbackModels: args.fallbackModels,
167929
+ callContext: `dreamer:${taskName}`
167234
167930
  });
167931
+ if (lostLease) {
167932
+ throw new Error(lostLeaseReason ?? `Dream lease lost during ${taskName}`);
167933
+ }
167235
167934
  const messagesResponse = await args.client.session.messages({
167236
167935
  path: { id: agentSessionId },
167237
167936
  query: { directory: args.sessionDirectory ?? args.projectIdentity }
@@ -167250,16 +167949,43 @@ async function runDream(args) {
167250
167949
  durationMs,
167251
167950
  result: taskResult
167252
167951
  });
167952
+ lastErrorSignature = null;
167953
+ consecutiveSameErrorFailures = 0;
167253
167954
  } catch (error51) {
167254
167955
  const durationMs = Date.now() - taskStartedAt;
167255
- const errorMsg = getErrorMessage(error51);
167256
- log(`[dreamer] task ${taskName}: failed after ${(durationMs / 1000).toFixed(1)}s — ${errorMsg}`);
167956
+ const errorDescription = describeError(error51);
167957
+ logWithStackHead(`[dreamer] task ${taskName}: failed after ${(durationMs / 1000).toFixed(1)}s — ${errorDescription.brief}`, errorDescription.stackHead);
167257
167958
  result.tasks.push({
167258
167959
  name: taskName,
167259
167960
  durationMs,
167260
167961
  result: null,
167261
- error: errorMsg
167962
+ error: errorDescription.brief
167262
167963
  });
167964
+ if (lostLease) {
167965
+ lastErrorSignature = null;
167966
+ consecutiveSameErrorFailures = 0;
167967
+ } else if (shouldSkipCircuitBreaker(error51, errorDescription.brief)) {
167968
+ lastErrorSignature = null;
167969
+ consecutiveSameErrorFailures = 0;
167970
+ } else {
167971
+ const signature = getCircuitBreakerSignature(error51, errorDescription.brief);
167972
+ if (signature === lastErrorSignature) {
167973
+ consecutiveSameErrorFailures += 1;
167974
+ } else {
167975
+ lastErrorSignature = signature;
167976
+ consecutiveSameErrorFailures = 1;
167977
+ }
167978
+ if (consecutiveSameErrorFailures >= CIRCUIT_BREAKER_THRESHOLD) {
167979
+ circuitBreakerTripped = true;
167980
+ log(`[dreamer] circuit breaker: ${consecutiveSameErrorFailures} consecutive ${signature} failures — aborting remaining tasks`);
167981
+ result.tasks.push({
167982
+ name: "circuit-breaker",
167983
+ durationMs: 0,
167984
+ result: "",
167985
+ error: `Aborted remaining tasks: ${consecutiveSameErrorFailures} consecutive ${signature} failures. Configure dreamer model/fallback_models in magic-context.jsonc.`
167986
+ });
167987
+ }
167988
+ }
167263
167989
  } finally {
167264
167990
  clearInterval(leaseRenewalInterval);
167265
167991
  if (agentSessionId) {
@@ -167271,10 +167997,32 @@ async function runDream(args) {
167271
167997
  });
167272
167998
  }
167273
167999
  }
168000
+ if (lostLease) {
168001
+ recordLeaseLostTask(`task ${taskName}`);
168002
+ break;
168003
+ }
168004
+ if (circuitBreakerTripped) {
168005
+ break;
168006
+ }
168007
+ }
168008
+ if (lostLease) {
168009
+ log("[dreamer] lease lost: skipping all post-task phases");
168010
+ recordLeaseLostTask("post-task phases");
168011
+ } else if (circuitBreakerTripped) {
168012
+ log("[dreamer] circuit breaker: skipping post-task phases");
168013
+ result.tasks.push({
168014
+ name: "post-task-phases",
168015
+ durationMs: 0,
168016
+ result: "",
168017
+ error: "Skipped post-task phases after circuit breaker tripped; configure dreamer model/fallback_models in magic-context.jsonc."
168018
+ });
167274
168019
  }
167275
- if (args.experimentalUserMemories?.enabled && Date.now() <= deadline) {
168020
+ if (!circuitBreakerTripped && !lostLease && args.experimentalUserMemories?.enabled && Date.now() <= deadline) {
167276
168021
  const umStart = Date.now();
167277
168022
  try {
168023
+ if (!verifyLeaseStillHeld("before user-memory review")) {
168024
+ throw new Error(lostLeaseReason ?? "Dream lease lost before user-memory review");
168025
+ }
167278
168026
  const reviewResult = await reviewUserMemories({
167279
168027
  db: args.db,
167280
168028
  client: args.client,
@@ -167282,8 +168030,12 @@ async function runDream(args) {
167282
168030
  sessionDirectory: args.sessionDirectory,
167283
168031
  holderId,
167284
168032
  deadline,
167285
- promotionThreshold: args.experimentalUserMemories.promotionThreshold
168033
+ promotionThreshold: args.experimentalUserMemories.promotionThreshold,
168034
+ fallbackModels: args.fallbackModels
167286
168035
  });
168036
+ if (!verifyLeaseStillHeld("after user-memory review")) {
168037
+ throw new Error(lostLeaseReason ?? "Dream lease lost after user-memory review");
168038
+ }
167287
168039
  const umOutput = `promoted=${reviewResult.promoted} merged=${reviewResult.merged} dismissed=${reviewResult.dismissed} consumed=${reviewResult.candidatesConsumed}`;
167288
168040
  if (reviewResult.promoted > 0 || reviewResult.merged > 0 || reviewResult.dismissed > 0) {
167289
168041
  log(`[dreamer] user-memories: ${umOutput}`);
@@ -167294,17 +168046,23 @@ async function runDream(args) {
167294
168046
  result: umOutput
167295
168047
  });
167296
168048
  } catch (error51) {
167297
- log(`[dreamer] user-memory review failed: ${getErrorMessage(error51)}`);
168049
+ const errorDescription = describeError(error51);
168050
+ logWithStackHead(`[dreamer] user-memory review failed: ${errorDescription.brief}`, errorDescription.stackHead);
167298
168051
  result.tasks.push({
167299
168052
  name: "user memories",
167300
168053
  durationMs: Date.now() - umStart,
167301
168054
  result: "",
167302
- error: getErrorMessage(error51)
168055
+ error: errorDescription.brief
167303
168056
  });
167304
168057
  }
168058
+ if (lostLease)
168059
+ recordLeaseLostTask("user-memory review");
167305
168060
  }
167306
- if (Date.now() <= deadline) {
168061
+ if (!circuitBreakerTripped && !lostLease && Date.now() <= deadline) {
167307
168062
  try {
168063
+ if (!verifyLeaseStillHeld("before smart-note evaluation")) {
168064
+ throw new Error(lostLeaseReason ?? "Dream lease lost before smart-note evaluation");
168065
+ }
167308
168066
  await evaluateSmartNotes({
167309
168067
  db: args.db,
167310
168068
  client: args.client,
@@ -167313,15 +168071,27 @@ async function runDream(args) {
167313
168071
  sessionDirectory: args.sessionDirectory,
167314
168072
  holderId,
167315
168073
  deadline,
167316
- result
168074
+ result,
168075
+ fallbackModels: args.fallbackModels,
168076
+ onLeaseLost: markLeaseLost,
168077
+ isLeaseLost: () => lostLease
167317
168078
  });
168079
+ if (!verifyLeaseStillHeld("after smart-note evaluation")) {
168080
+ throw new Error(lostLeaseReason ?? "Dream lease lost after smart-note evaluation");
168081
+ }
167318
168082
  } catch (error51) {
167319
- log(`[dreamer] smart note evaluation failed: ${getErrorMessage(error51)}`);
168083
+ const errorDescription = describeError(error51);
168084
+ logWithStackHead(`[dreamer] smart note evaluation failed: ${errorDescription.brief}`, errorDescription.stackHead);
167320
168085
  }
168086
+ if (lostLease)
168087
+ recordLeaseLostTask("smart-note evaluation");
167321
168088
  }
167322
- if (args.experimentalPinKeyFiles?.enabled && Date.now() <= deadline) {
168089
+ if (!circuitBreakerTripped && !lostLease && args.experimentalPinKeyFiles?.enabled && Date.now() <= deadline) {
167323
168090
  const kfStart = Date.now();
167324
168091
  try {
168092
+ if (!verifyLeaseStillHeld("before key-file identification")) {
168093
+ throw new Error(lostLeaseReason ?? "Dream lease lost before key-file identification");
168094
+ }
167325
168095
  await identifyKeyFiles({
167326
168096
  db: args.db,
167327
168097
  client: args.client,
@@ -167330,22 +168100,31 @@ async function runDream(args) {
167330
168100
  sessionDirectory: args.sessionDirectory ?? args.projectIdentity,
167331
168101
  holderId,
167332
168102
  deadline,
167333
- config: args.experimentalPinKeyFiles
168103
+ config: args.experimentalPinKeyFiles,
168104
+ fallbackModels: args.fallbackModels,
168105
+ onLeaseLost: markLeaseLost,
168106
+ isLeaseLost: () => lostLease
167334
168107
  });
168108
+ if (!verifyLeaseStillHeld("after key-file identification")) {
168109
+ throw new Error(lostLeaseReason ?? "Dream lease lost after key-file identification");
168110
+ }
167335
168111
  result.tasks.push({
167336
168112
  name: "key files",
167337
168113
  durationMs: Date.now() - kfStart,
167338
168114
  result: "completed"
167339
168115
  });
167340
168116
  } catch (error51) {
167341
- log(`[key-files] identification phase failed: ${getErrorMessage(error51)}`);
168117
+ const errorDescription = describeError(error51);
168118
+ logWithStackHead(`[key-files] identification phase failed: ${errorDescription.brief}`, errorDescription.stackHead);
167342
168119
  result.tasks.push({
167343
168120
  name: "key files",
167344
168121
  durationMs: Date.now() - kfStart,
167345
168122
  result: "",
167346
- error: getErrorMessage(error51)
168123
+ error: errorDescription.brief
167347
168124
  });
167348
168125
  }
168126
+ if (lostLease)
168127
+ recordLeaseLostTask("key-file identification");
167349
168128
  }
167350
168129
  } finally {
167351
168130
  releaseLease(args.db, holderId);
@@ -167378,9 +168157,15 @@ async function runDream(args) {
167378
168157
  smartNotesPending: result.smartNotesPending,
167379
168158
  memoryChanges: persistedMemoryChanges
167380
168159
  });
167381
- const POST_TASK_NAMES = new Set(["smart-notes", "user memories", "key files"]);
168160
+ const POST_TASK_NAMES = new Set([
168161
+ "smart-notes",
168162
+ "user memories",
168163
+ "key files",
168164
+ "post-task-phases",
168165
+ "circuit-breaker"
168166
+ ]);
167382
168167
  const hasSuccessfulTask = result.tasks.some((t) => !t.error && !POST_TASK_NAMES.has(t.name));
167383
- if (hasSuccessfulTask) {
168168
+ if (hasSuccessfulTask && !lostLease) {
167384
168169
  setDreamState(args.db, `last_dream_at:${args.projectIdentity}`, String(result.finishedAt));
167385
168170
  setDreamState(args.db, "last_dream_at", String(result.finishedAt));
167386
168171
  }
@@ -167429,9 +168214,11 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
167429
168214
  try {
167430
168215
  if (!renewLease(args.db, args.holderId)) {
167431
168216
  log("[dreamer] smart notes: lease renewal failed — aborting");
168217
+ args.onLeaseLost?.("smart notes");
167432
168218
  abortController.abort();
167433
168219
  }
167434
- } catch {
168220
+ } catch (error51) {
168221
+ args.onLeaseLost?.("smart notes", error51);
167435
168222
  abortController.abort();
167436
168223
  }
167437
168224
  }, 60000);
@@ -167457,7 +168244,12 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
167457
168244
  system: DREAMER_SYSTEM_PROMPT,
167458
168245
  parts: [{ type: "text", text: evaluationPrompt, synthetic: true }]
167459
168246
  }
167460
- }, { timeoutMs: Math.min(remainingMs, 300000), signal: abortController.signal });
168247
+ }, {
168248
+ timeoutMs: Math.min(remainingMs, 300000),
168249
+ signal: abortController.signal,
168250
+ fallbackModels: args.fallbackModels,
168251
+ callContext: "dreamer:smart-notes"
168252
+ });
167461
168253
  const messagesResponse = await args.client.session.messages({
167462
168254
  path: { id: agentSessionId },
167463
168255
  query: { directory: args.sessionDirectory ?? args.projectIdentity }
@@ -167516,15 +168308,15 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
167516
168308
  });
167517
168309
  } catch (error51) {
167518
168310
  const durationMs = Date.now() - taskStartedAt;
167519
- const errorMsg = getErrorMessage(error51);
168311
+ const errorDescription = describeError(error51);
167520
168312
  args.result.smartNotesSurfaced = 0;
167521
168313
  args.result.smartNotesPending = pendingNotes.length;
167522
- log(`[dreamer] smart notes: failed after ${(durationMs / 1000).toFixed(1)}s — ${errorMsg}`);
168314
+ logWithStackHead(`[dreamer] smart notes: failed after ${(durationMs / 1000).toFixed(1)}s — ${errorDescription.brief}`, errorDescription.stackHead);
167523
168315
  args.result.tasks.push({
167524
168316
  name: "smart-notes",
167525
168317
  durationMs,
167526
168318
  result: null,
167527
- error: errorMsg
168319
+ error: errorDescription.brief
167528
168320
  });
167529
168321
  } finally {
167530
168322
  clearInterval(leaseInterval);
@@ -167557,7 +168349,8 @@ async function processDreamQueue(args) {
167557
168349
  maxRuntimeMinutes: args.maxRuntimeMinutes,
167558
168350
  sessionDirectory: projectDirectory,
167559
168351
  experimentalUserMemories: args.experimentalUserMemories,
167560
- experimentalPinKeyFiles: args.experimentalPinKeyFiles
168352
+ experimentalPinKeyFiles: args.experimentalPinKeyFiles,
168353
+ fallbackModels: args.fallbackModels
167561
168354
  });
167562
168355
  } catch (error51) {
167563
168356
  log(`[dreamer] runDream threw for ${entry.projectIdentity}: ${getErrorMessage(error51)}`);
@@ -168165,6 +168958,7 @@ function searchGitCommitsSync(db, projectPath, query, options) {
168165
168958
  init_embedding();
168166
168959
  init_project_identity();
168167
168960
  init_logger();
168961
+ init_resolve_fallbacks();
168168
168962
  await init_storage();
168169
168963
  var DREAM_TIMER_INTERVAL_MS = 15 * 60 * 1000;
168170
168964
  var activeTimer = null;
@@ -168251,7 +169045,8 @@ async function sweepProject(reg, origin) {
168251
169045
  maxRuntimeMinutes: reg.dreamerConfig.max_runtime_minutes,
168252
169046
  experimentalUserMemories: reg.experimentalUserMemories,
168253
169047
  experimentalPinKeyFiles: reg.experimentalPinKeyFiles,
168254
- projectIdentity: registrationIdentity
169048
+ projectIdentity: registrationIdentity,
169049
+ fallbackModels: resolveFallbackChain(DREAMER_AGENT, reg.dreamerConfig.fallback_models)
168255
169050
  });
168256
169051
  } catch (error51) {
168257
169052
  log(`[dreamer] timer-triggered queue processing failed for ${reg.directory}:`, error51);
@@ -168779,6 +169574,7 @@ function createTagger() {
168779
169574
  init_magic_context();
168780
169575
  init_project_identity();
168781
169576
  init_logger();
169577
+ init_resolve_fallbacks();
168782
169578
  await init_storage();
168783
169579
 
168784
169580
  // src/hooks/magic-context/command-handler.ts
@@ -169259,7 +170055,8 @@ Dreaming is not configured for this project.`, {});
169259
170055
  maxRuntimeMinutes: deps.dreamer.config.max_runtime_minutes,
169260
170056
  experimentalUserMemories: deps.dreamer.experimentalUserMemories,
169261
170057
  experimentalPinKeyFiles: deps.dreamer.experimentalPinKeyFiles,
169262
- projectIdentity: deps.dreamer.projectPath
170058
+ projectIdentity: deps.dreamer.projectPath,
170059
+ fallbackModels: deps.dreamer.fallbackModels
169263
170060
  });
169264
170061
  await deps.sendNotification(sessionId, result ? summarizeDreamResult(result) : "Dream queued, but another worker is already processing the queue.", {});
169265
170062
  throwSentinel("CTX-DREAM");
@@ -169555,113 +170352,8 @@ function clearSessionTracking(sessionId) {
169555
170352
  }
169556
170353
  }
169557
170354
 
169558
- // src/features/magic-context/overflow-detection.ts
169559
- var OVERFLOW_PATTERNS = [
169560
- /prompt is too long/i,
169561
- /input is too long for requested model/i,
169562
- /exceeds the context window/i,
169563
- /input token count.*exceeds the maximum/i,
169564
- /maximum prompt length is \d+/i,
169565
- /reduce the length of the messages/i,
169566
- /maximum context length is \d+ tokens/i,
169567
- /exceeds the limit of \d+/i,
169568
- /exceeds the available context size/i,
169569
- /greater than the context length/i,
169570
- /context window exceeds limit/i,
169571
- /exceeded model token limit/i,
169572
- /context[_ ]length[_ ]exceeded/i,
169573
- /request entity too large/i,
169574
- /context length is only \d+ tokens/i,
169575
- /input length.*exceeds.*context length/i,
169576
- /prompt too long; exceeded (?:max )?context length/i,
169577
- /too large for model with \d+ maximum context length/i,
169578
- /model_context_window_exceeded/i,
169579
- /context size has been exceeded/i
169580
- ];
169581
- var LIMIT_EXTRACTION_PATTERNS = [
169582
- /maximum prompt length is (\d+)/i,
169583
- /maximum context length is (\d+) tokens?/i,
169584
- /context length is only (\d+) tokens?/i,
169585
- /exceeds the limit of (\d+)/i,
169586
- /too large for model with (\d+) maximum context length/i,
169587
- /context size.*(\d+) tokens?/i,
169588
- /exceeds? the context length of (\d+)/i,
169589
- /max(?:imum)?.*context.*?(\d+)/i
169590
- ];
169591
- var MIN_PLAUSIBLE_LIMIT = 1024;
169592
- var MAX_PLAUSIBLE_LIMIT = 1e7;
169593
- function extractErrorMessage(error51) {
169594
- if (!error51)
169595
- return "";
169596
- if (typeof error51 === "string")
169597
- return error51;
169598
- if (typeof error51 === "object") {
169599
- const obj = error51;
169600
- const nested = obj.error;
169601
- if (nested && typeof nested.message === "string" && nested.message.length > 0) {
169602
- return nested.message;
169603
- }
169604
- }
169605
- if (error51 instanceof Error)
169606
- return error51.message;
169607
- if (typeof error51 === "object") {
169608
- const obj = error51;
169609
- if (typeof obj.message === "string")
169610
- return obj.message;
169611
- if (typeof obj.responseBody === "string")
169612
- return obj.responseBody;
169613
- try {
169614
- return JSON.stringify(error51);
169615
- } catch {
169616
- return String(error51);
169617
- }
169618
- }
169619
- return String(error51);
169620
- }
169621
- function detectOverflow(error51) {
169622
- const message = extractErrorMessage(error51);
169623
- if (!message) {
169624
- return { isOverflow: false };
169625
- }
169626
- const hasStatus413 = /\b413\b/.test(message) && /(entity|payload|context|prompt)/i.test(message);
169627
- let matched;
169628
- for (const pattern of OVERFLOW_PATTERNS) {
169629
- if (pattern.test(message)) {
169630
- matched = pattern;
169631
- break;
169632
- }
169633
- }
169634
- if (!matched && !hasStatus413) {
169635
- return { isOverflow: false };
169636
- }
169637
- const reportedLimit = parseReportedLimit(message);
169638
- return {
169639
- isOverflow: true,
169640
- reportedLimit,
169641
- matchedPattern: matched?.source
169642
- };
169643
- }
169644
- function parseReportedLimit(message) {
169645
- if (!message)
169646
- return;
169647
- for (const pattern of LIMIT_EXTRACTION_PATTERNS) {
169648
- const match = message.match(pattern);
169649
- if (!match)
169650
- continue;
169651
- const raw = match[1];
169652
- if (!raw)
169653
- continue;
169654
- const value = Number.parseInt(raw, 10);
169655
- if (!Number.isFinite(value))
169656
- continue;
169657
- if (value < MIN_PLAUSIBLE_LIMIT || value > MAX_PLAUSIBLE_LIMIT)
169658
- continue;
169659
- return value;
169660
- }
169661
- return;
169662
- }
169663
-
169664
170355
  // src/hooks/magic-context/event-handler.ts
170356
+ init_overflow_detection();
169665
170357
  init_storage_meta_persisted();
169666
170358
  init_logger();
169667
170359
  await __promiseAll([
@@ -169774,6 +170466,15 @@ init_project_identity();
169774
170466
  init_logger();
169775
170467
  await init_storage();
169776
170468
 
170469
+ // src/hooks/magic-context/cache-busting-signals.ts
170470
+ function canConsumeDeferredOnThisPass(args) {
170471
+ if (args.justAwaitedPublication)
170472
+ return true;
170473
+ if (args.activeRunBlocksMaterialization)
170474
+ return false;
170475
+ return args.schedulerDecision === "execute" || args.contextPercentage >= FORCE_MATERIALIZE_PERCENTAGE;
170476
+ }
170477
+
169777
170478
  // src/hooks/magic-context/caveman-cleanup.ts
169778
170479
  init_shared();
169779
170480
  init_caveman();
@@ -170521,6 +171222,9 @@ function clearCompressorCooldown(sessionId) {
170521
171222
  async function runCompartmentPhase(args) {
170522
171223
  let pendingCompartmentInjection = args.pendingCompartmentInjection;
170523
171224
  let compartmentInProgress = args.sessionMeta.compartmentInProgress;
171225
+ let published = false;
171226
+ let justAwaitedPublication = false;
171227
+ let rebuiltHistoryThisPass = false;
170524
171228
  let lastCompartmentEnd = null;
170525
171229
  let rawMessageCount = null;
170526
171230
  let cachedProtectedTailStart = null;
@@ -170548,13 +171252,19 @@ async function runCompartmentPhase(args) {
170548
171252
  sessionLog(args.sessionId, reason);
170549
171253
  const timeoutMs = args.historianTimeoutMs ?? 120000;
170550
171254
  const timeout = new Promise((resolve4) => setTimeout(() => resolve4("timeout"), timeoutMs));
170551
- const result = await Promise.race([activeRun.then(() => "done"), timeout]);
171255
+ const result = await Promise.race([activeRun.promise.then(() => "done"), timeout]);
170552
171256
  if (result === "timeout") {
170553
171257
  sessionLog(args.sessionId, `transform: compartment await timed out after ${timeoutMs}ms — proceeding without waiting`);
170554
171258
  return "timed_out";
170555
171259
  }
170556
171260
  sessionLog(args.sessionId, "transform: compartment agent completed, refreshing compartment coverage");
170557
- pendingCompartmentInjection = prepareCompartmentInjection(args.db, args.resolvedSessionId, args.messages, args.cacheAlreadyBusting ?? false, args.projectPath, args.injectionBudgetTokens, args.experimentalTemporalAwareness);
171261
+ justAwaitedPublication = activeRun.published;
171262
+ published = published || activeRun.published;
171263
+ const historyReprepareShouldBust = activeRun.published && args.deferredHistoryRefreshSessions.has(args.sessionId);
171264
+ pendingCompartmentInjection = prepareCompartmentInjection(args.db, args.resolvedSessionId, args.messages, historyReprepareShouldBust, args.projectPath, args.injectionBudgetTokens, args.experimentalTemporalAwareness);
171265
+ if (historyReprepareShouldBust) {
171266
+ rebuiltHistoryThisPass = true;
171267
+ }
170558
171268
  return "completed";
170559
171269
  }
170560
171270
  if (args.canRunCompartments && args.sessionMeta.compartmentInProgress && !getActiveCompartmentRun(args.sessionId)) {
@@ -170575,6 +171285,7 @@ async function runCompartmentPhase(args) {
170575
171285
  historianChunkTokens: args.historianChunkTokens,
170576
171286
  historyBudgetTokens: args.historyBudgetTokens,
170577
171287
  historianTimeoutMs: args.historianTimeoutMs,
171288
+ fallbackModels: args.fallbackModels,
170578
171289
  directory: args.compartmentDirectory,
170579
171290
  fallbackModelId: args.fallbackModelId,
170580
171291
  getNotificationParams: args.getNotificationParams,
@@ -170585,7 +171296,8 @@ async function runCompartmentPhase(args) {
170585
171296
  compressorMaxMergeDepth: args.compressorMaxMergeDepth,
170586
171297
  memoryEnabled: args.memoryEnabled,
170587
171298
  autoPromote: args.autoPromote,
170588
- onInjectionCacheCleared: args.onInjectionCacheCleared
171299
+ onCompartmentStatePublished: args.onCompartmentStatePublished,
171300
+ preserveInjectionCacheUntilConsumed: true
170589
171301
  });
170590
171302
  compartmentInProgress = true;
170591
171303
  }
@@ -170602,6 +171314,7 @@ async function runCompartmentPhase(args) {
170602
171314
  historianChunkTokens: args.historianChunkTokens,
170603
171315
  historyBudgetTokens: args.historyBudgetTokens,
170604
171316
  historianTimeoutMs: args.historianTimeoutMs,
171317
+ fallbackModels: args.fallbackModels,
170605
171318
  directory: args.compartmentDirectory,
170606
171319
  fallbackModelId: args.fallbackModelId,
170607
171320
  getNotificationParams: args.getNotificationParams,
@@ -170612,7 +171325,8 @@ async function runCompartmentPhase(args) {
170612
171325
  compressorMaxMergeDepth: args.compressorMaxMergeDepth,
170613
171326
  memoryEnabled: args.memoryEnabled,
170614
171327
  autoPromote: args.autoPromote,
170615
- onInjectionCacheCleared: args.onInjectionCacheCleared
171328
+ onCompartmentStatePublished: args.onCompartmentStatePublished,
171329
+ preserveInjectionCacheUntilConsumed: true
170616
171330
  });
170617
171331
  activeRun = getActiveCompartmentRun(args.sessionId);
170618
171332
  } else if (!activeRun && hasEligibleHistoryForCompartment()) {
@@ -170632,7 +171346,7 @@ async function runCompartmentPhase(args) {
170632
171346
  }
170633
171347
  }
170634
171348
  }
170635
- if (args.cacheAlreadyBusting && args.historyBudgetTokens && args.historyBudgetTokens > 0 && args.client && !compartmentInProgress && !awaitedCompartmentRun && !isCompressorOnCooldown(args.sessionId, args.compressorCooldownMs ?? DEFAULT_COMPRESSOR_COOLDOWN_MS)) {
171349
+ if (args.safeForBackgroundCompression && !args.suppressBackgroundCompressionThisPass && args.historyBudgetTokens && args.historyBudgetTokens > 0 && args.client && !compartmentInProgress && !awaitedCompartmentRun && !isCompressorOnCooldown(args.sessionId, args.compressorCooldownMs ?? DEFAULT_COMPRESSOR_COOLDOWN_MS)) {
170636
171350
  markCompressorRun(args.sessionId);
170637
171351
  const compressorPromise = runCompressionPassIfNeeded({
170638
171352
  client: args.client,
@@ -170641,10 +171355,14 @@ async function runCompartmentPhase(args) {
170641
171355
  directory: args.compartmentDirectory,
170642
171356
  historyBudgetTokens: args.historyBudgetTokens,
170643
171357
  historianTimeoutMs: args.historianTimeoutMs,
171358
+ fallbackModels: args.fallbackModels,
170644
171359
  minCompartmentRatio: args.compressorMinCompartmentRatio,
170645
171360
  maxMergeDepth: args.compressorMaxMergeDepth
170646
171361
  }).then((compressed) => {
170647
171362
  if (compressed) {
171363
+ markActiveCompartmentRunPublished(args.sessionId);
171364
+ published = true;
171365
+ args.deferredHistoryRefreshSessions.add(args.sessionId);
170648
171366
  sessionLog(args.sessionId, "independent compressor completed in background — compressed history will appear on next cache-busting pass");
170649
171367
  }
170650
171368
  }).catch((error51) => {
@@ -170652,7 +171370,14 @@ async function runCompartmentPhase(args) {
170652
171370
  });
170653
171371
  registerActiveCompartmentRun(args.sessionId, compressorPromise);
170654
171372
  }
170655
- return { pendingCompartmentInjection, awaitedCompartmentRun, compartmentInProgress };
171373
+ return {
171374
+ pendingCompartmentInjection,
171375
+ awaitedCompartmentRun,
171376
+ compartmentInProgress,
171377
+ published,
171378
+ justAwaitedPublication,
171379
+ rebuiltHistoryThisPass
171380
+ };
170656
171381
  }
170657
171382
 
170658
171383
  // src/hooks/magic-context/transform-context-state.ts
@@ -170738,6 +171463,46 @@ function countMessagesSinceLastUser(messages) {
170738
171463
  }
170739
171464
  return messagesSinceLastUser;
170740
171465
  }
171466
+ function injectToolPartIntoLatestAssistant(messages, part) {
171467
+ for (let index = messages.length - 1;index >= 0; index -= 1) {
171468
+ const message = messages[index];
171469
+ if (message.info.role !== "assistant")
171470
+ continue;
171471
+ if (typeof message.info.id !== "string")
171472
+ continue;
171473
+ if (hasToolPartWithCallId(message, part.callID)) {
171474
+ return message.info.id;
171475
+ }
171476
+ message.parts.push(part);
171477
+ return message.info.id;
171478
+ }
171479
+ return null;
171480
+ }
171481
+ function injectToolPartIntoAssistantById(messages, messageId, part) {
171482
+ for (const message of messages) {
171483
+ if (message.info.id !== messageId)
171484
+ continue;
171485
+ if (message.info.role !== "assistant")
171486
+ continue;
171487
+ if (hasToolPartWithCallId(message, part.callID))
171488
+ return true;
171489
+ message.parts.push(part);
171490
+ return true;
171491
+ }
171492
+ return false;
171493
+ }
171494
+ function hasToolPartWithCallId(message, callId) {
171495
+ for (const part of message.parts) {
171496
+ if (part === null || typeof part !== "object")
171497
+ continue;
171498
+ const p = part;
171499
+ if (p.type !== "tool")
171500
+ continue;
171501
+ if (p.callID === callId)
171502
+ return true;
171503
+ }
171504
+ return false;
171505
+ }
170741
171506
  function appendReminderToUserMessage(message, reminder) {
170742
171507
  for (const part of message.parts) {
170743
171508
  if (!isTextPart(part)) {
@@ -171974,7 +172739,10 @@ function clearAutoSearchForSession(sessionId) {
171974
172739
  }
171975
172740
 
171976
172741
  // src/hooks/magic-context/transform-postprocess-phase.ts
171977
- await init_compartment_runner();
172742
+ await __promiseAll([
172743
+ init_compaction_marker_manager(),
172744
+ init_compartment_runner()
172745
+ ]);
171978
172746
 
171979
172747
  // src/hooks/magic-context/heuristic-cleanup.ts
171980
172748
  init_shared();
@@ -172247,6 +173015,84 @@ function isVisibleNoteReadPart(part) {
172247
173015
  return false;
172248
173016
  }
172249
173017
 
173018
+ // src/hooks/magic-context/todo-view.ts
173019
+ import { createHash as createHash5 } from "node:crypto";
173020
+ var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
173021
+ var TITLE_DONE_STATUSES = new Set(["completed"]);
173022
+ var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
173023
+ function normalizeTodoStateJson(todos) {
173024
+ if (!Array.isArray(todos))
173025
+ return null;
173026
+ const normalized = [];
173027
+ for (const todo of todos) {
173028
+ if (!isTodoItem(todo))
173029
+ return null;
173030
+ normalized.push({
173031
+ content: todo.content,
173032
+ status: todo.status,
173033
+ priority: todo.priority
173034
+ });
173035
+ }
173036
+ return JSON.stringify(normalized);
173037
+ }
173038
+ function buildSyntheticTodoPart(stateJson) {
173039
+ const todos = parseTodoState(stateJson);
173040
+ if (todos === null || todos.length === 0)
173041
+ return null;
173042
+ if (todos.every((t) => TERMINAL_STATUSES.has(t.status)))
173043
+ return null;
173044
+ const callID = computeSyntheticCallId(stateJson);
173045
+ const activeCount = todos.filter((t) => !TITLE_DONE_STATUSES.has(t.status)).length;
173046
+ const output = JSON.stringify(todos, null, 2);
173047
+ const ts = 0;
173048
+ return {
173049
+ type: "tool",
173050
+ callID,
173051
+ tool: "todowrite",
173052
+ state: {
173053
+ status: "completed",
173054
+ input: { todos },
173055
+ output,
173056
+ title: `${activeCount} todos`,
173057
+ metadata: { todos, truncated: false },
173058
+ time: { start: ts, end: ts }
173059
+ },
173060
+ syntheticTodoMarker: true
173061
+ };
173062
+ }
173063
+ function computeSyntheticCallId(stateJson) {
173064
+ const hash2 = createHash5("sha256").update(stateJson).digest("hex").slice(0, 16);
173065
+ return `${SYNTHETIC_CALL_ID_PREFIX}${hash2}`;
173066
+ }
173067
+ function parseTodoState(stateJson) {
173068
+ if (stateJson.length === 0)
173069
+ return null;
173070
+ try {
173071
+ const parsed = JSON.parse(stateJson);
173072
+ if (!Array.isArray(parsed))
173073
+ return null;
173074
+ const result = [];
173075
+ for (const item of parsed) {
173076
+ if (!isTodoItem(item))
173077
+ continue;
173078
+ result.push({
173079
+ content: item.content,
173080
+ status: item.status,
173081
+ priority: item.priority
173082
+ });
173083
+ }
173084
+ return result;
173085
+ } catch {
173086
+ return null;
173087
+ }
173088
+ }
173089
+ function isTodoItem(value) {
173090
+ if (value === null || typeof value !== "object")
173091
+ return false;
173092
+ const todo = value;
173093
+ return typeof todo.content === "string" && typeof todo.status === "string" && typeof todo.priority === "string";
173094
+ }
173095
+
172250
173096
  // src/hooks/magic-context/transform-stage-logger.ts
172251
173097
  init_logger();
172252
173098
  function logTransformTiming(sessionId, stage, startMs, extra) {
@@ -172256,26 +173102,34 @@ function logTransformTiming(sessionId, stage, startMs, extra) {
172256
173102
  }
172257
173103
 
172258
173104
  // src/hooks/magic-context/transform-postprocess-phase.ts
173105
+ var DEGRADE_CACHE_WARNING_THRESHOLD = 10;
173106
+ var degradedCacheCountBySession = new Map;
173107
+ function resetDegradedCacheCount(sessionId) {
173108
+ degradedCacheCountBySession.delete(sessionId);
173109
+ }
172259
173110
  async function runPostTransformPhase(args) {
172260
173111
  let didMutateFromPendingOperations = false;
172261
173112
  const isExplicitFlush = args.pendingMaterializationSessions.has(args.sessionId);
173113
+ const deferredMaterializationWasPending = args.deferredMaterializationSessions.has(args.sessionId);
172262
173114
  const alreadyRanThisTurn = args.currentTurnId !== null && args.lastHeuristicsTurnId.get(args.sessionId) === args.currentTurnId;
172263
173115
  const forceMaterialization = args.fullFeatureMode && args.contextUsage.percentage >= args.forceMaterializationPercentage;
172264
173116
  const activeCompartmentRun = args.canRunCompartments ? getActiveCompartmentRun(args.sessionId) : undefined;
172265
173117
  const compartmentRunning = args.canRunCompartments && !args.awaitedCompartmentRun && activeCompartmentRun !== undefined;
172266
173118
  const emergencyBypassCompartmentGate = forceMaterialization;
172267
- const shouldReadPendingOps = isExplicitFlush || args.schedulerDecision === "execute" || forceMaterialization || compartmentRunning;
173119
+ const deferredMaterialize = args.canConsumeDeferredLate && deferredMaterializationWasPending;
173120
+ const materializationRequested = isExplicitFlush || deferredMaterialize;
173121
+ const shouldReadPendingOps = materializationRequested || args.schedulerDecision === "execute" || forceMaterialization || compartmentRunning;
172268
173122
  const pendingOps = shouldReadPendingOps ? getPendingOps(args.db, args.sessionId) : [];
172269
173123
  const hasPendingUserOps = pendingOps.length > 0;
172270
- const shouldApplyPendingOps = (args.schedulerDecision === "execute" || isExplicitFlush || forceMaterialization) && (!compartmentRunning || emergencyBypassCompartmentGate);
172271
- const shouldRunHeuristics = (!compartmentRunning || emergencyBypassCompartmentGate) && (isExplicitFlush || forceMaterialization || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
173124
+ const shouldApplyPendingOps = (args.schedulerDecision === "execute" || materializationRequested || forceMaterialization) && (!compartmentRunning || emergencyBypassCompartmentGate);
173125
+ const shouldRunHeuristics = (!compartmentRunning || emergencyBypassCompartmentGate) && (materializationRequested || forceMaterialization || args.schedulerDecision === "execute" && (!alreadyRanThisTurn || !args.fullFeatureMode));
172272
173126
  const isCacheBustingPass = shouldApplyPendingOps || shouldRunHeuristics;
172273
173127
  if (shouldRunHeuristics) {
172274
173128
  const subagentRerun = !args.fullFeatureMode && alreadyRanThisTurn && args.schedulerDecision === "execute" && !isExplicitFlush && !forceMaterialization;
172275
- const reason = isExplicitFlush ? "explicit_flush" : forceMaterialization ? `force_materialization (${args.contextUsage.percentage.toFixed(1)}% >= ${args.forceMaterializationPercentage}%)` : subagentRerun ? `scheduler_execute_subagent_rerun (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
173129
+ const reason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : forceMaterialization ? `force_materialization (${args.contextUsage.percentage.toFixed(1)}% >= ${args.forceMaterializationPercentage}%)` : subagentRerun ? `scheduler_execute_subagent_rerun (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
172276
173130
  sessionLog(args.sessionId, `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=${args.currentTurnId}`);
172277
173131
  }
172278
- if (alreadyRanThisTurn && args.schedulerDecision === "execute" && !isExplicitFlush && args.fullFeatureMode) {
173132
+ if (alreadyRanThisTurn && args.schedulerDecision === "execute" && !materializationRequested && args.fullFeatureMode) {
172279
173133
  sessionLog(args.sessionId, `transform: skipping heuristics (already ran for turn ${args.currentTurnId})`);
172280
173134
  }
172281
173135
  if (compartmentRunning && hasPendingUserOps) {
@@ -172285,9 +173139,11 @@ async function runPostTransformPhase(args) {
172285
173139
  sessionLog(args.sessionId, "transform: deferring pending ops — compartment agent in progress");
172286
173140
  }
172287
173141
  }
173142
+ let explicitMaterializedSuccessfully = false;
173143
+ let deferredMaterializedSuccessfully = false;
172288
173144
  try {
172289
173145
  if (shouldApplyPendingOps) {
172290
- const applyReason = isExplicitFlush ? "explicit_flush" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
173146
+ const applyReason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
172291
173147
  sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
172292
173148
  const pendingCountBefore = pendingOps.length;
172293
173149
  const tApply = performance.now();
@@ -172349,7 +173205,7 @@ async function runPostTransformPhase(args) {
172349
173205
  args.lastHeuristicsTurnId.set(args.sessionId, args.currentTurnId);
172350
173206
  }
172351
173207
  }
172352
- if (args.schedulerDecision === "execute" && !isExplicitFlush) {
173208
+ if (args.schedulerDecision === "execute" && !materializationRequested) {
172353
173209
  updateSessionMeta(args.db, args.sessionId, { lastResponseTime: Date.now() });
172354
173210
  }
172355
173211
  args.batch?.finalize();
@@ -172357,6 +173213,12 @@ async function runPostTransformPhase(args) {
172357
173213
  if (args.sessionMeta.lastTransformError !== null) {
172358
173214
  updateSessionMeta(args.db, args.sessionId, { lastTransformError: null });
172359
173215
  }
173216
+ if (shouldRunHeuristics) {
173217
+ if (isExplicitFlush)
173218
+ explicitMaterializedSuccessfully = true;
173219
+ if (deferredMaterialize)
173220
+ deferredMaterializedSuccessfully = true;
173221
+ }
172360
173222
  } catch (error51) {
172361
173223
  sessionLog(args.sessionId, "transform failed applying pending operations:", error51);
172362
173224
  updateSessionMeta(args.db, args.sessionId, { lastTransformError: getErrorMessage(error51) });
@@ -172488,6 +173350,72 @@ async function runPostTransformPhase(args) {
172488
173350
  const anchoredMessageId = appendReminderToLatestUserMessage(args.messages, noteInstruction);
172489
173351
  markNoteNudgeDelivered(args.db, args.sessionId, noteInstruction, anchoredMessageId);
172490
173352
  }
173353
+ if (args.fullFeatureMode) {
173354
+ const persistedAnchor = getPersistedTodoSyntheticAnchor(args.db, args.sessionId);
173355
+ if (isCacheBustingPass) {
173356
+ const part = buildSyntheticTodoPart(args.sessionMeta.lastTodoState);
173357
+ if (part === null) {
173358
+ if (persistedAnchor) {
173359
+ clearPersistedTodoSyntheticAnchor(args.db, args.sessionId);
173360
+ }
173361
+ } else if (persistedAnchor && persistedAnchor.callId === part.callID && injectToolPartIntoAssistantById(args.messages, persistedAnchor.messageId, part)) {
173362
+ if (persistedAnchor.stateJson.length === 0) {
173363
+ setPersistedTodoSyntheticAnchor(args.db, args.sessionId, persistedAnchor.callId, persistedAnchor.messageId, args.sessionMeta.lastTodoState);
173364
+ }
173365
+ } else {
173366
+ const anchoredMessageId = injectToolPartIntoLatestAssistant(args.messages, part);
173367
+ if (anchoredMessageId) {
173368
+ setPersistedTodoSyntheticAnchor(args.db, args.sessionId, part.callID, anchoredMessageId, args.sessionMeta.lastTodoState);
173369
+ } else if (persistedAnchor) {
173370
+ clearPersistedTodoSyntheticAnchor(args.db, args.sessionId);
173371
+ }
173372
+ }
173373
+ } else if (persistedAnchor && persistedAnchor.stateJson.length > 0) {
173374
+ const part = buildSyntheticTodoPart(persistedAnchor.stateJson);
173375
+ if (part !== null && part.callID === persistedAnchor.callId) {
173376
+ injectToolPartIntoAssistantById(args.messages, persistedAnchor.messageId, part);
173377
+ }
173378
+ }
173379
+ }
173380
+ const explicitRebuildHappened = args.historyRefreshExplicitBeforePrepare && args.rebuiltHistoryFromInitialPrepare;
173381
+ const materializationSatisfied = !deferredMaterializationWasPending || explicitMaterializedSuccessfully || deferredMaterializedSuccessfully;
173382
+ const historyWasConsumedThisPass = args.historyRebuiltThisPass && (args.canConsumeDeferredLate || args.phaseJustAwaitedPublication || explicitRebuildHappened) && materializationSatisfied;
173383
+ if (args.compartmentInjectionRebuiltFromDb && args.pendingCompartmentInjection) {
173384
+ if (args.pendingCompartmentInjection.compartmentEndMessageId === null) {
173385
+ const nextCount = (degradedCacheCountBySession.get(args.sessionId) ?? 0) + 1;
173386
+ degradedCacheCountBySession.set(args.sessionId, nextCount);
173387
+ if (nextCount === DEGRADE_CACHE_WARNING_THRESHOLD) {
173388
+ sessionLog(args.sessionId, `WARNING: compartment injection cache has rebuilt with a degraded null boundary ${nextCount} consecutive times; investigate missing boundary messages`);
173389
+ }
173390
+ } else {
173391
+ degradedCacheCountBySession.delete(args.sessionId);
173392
+ }
173393
+ }
173394
+ let suppressV12HistoryDrain = false;
173395
+ if (historyWasConsumedThisPass && args.deferredHistoryRefreshSessions.has(args.sessionId)) {
173396
+ const pending = getPendingCompactionMarkerState(args.db, args.sessionId);
173397
+ if (pending) {
173398
+ const outcome = applyDeferredCompactionMarker(args.db, args.sessionId, pending, args.sessionDirectory);
173399
+ switch (outcome.kind) {
173400
+ case "applied":
173401
+ case "already-current":
173402
+ case "stale-skip":
173403
+ clearPendingCompactionMarkerStateIf(args.db, args.sessionId, pending);
173404
+ break;
173405
+ case "retryable-failure":
173406
+ sessionLog(args.sessionId, "compaction-marker drain: retryable failure; preserving deferred history refresh signal", outcome.error);
173407
+ suppressV12HistoryDrain = true;
173408
+ break;
173409
+ }
173410
+ }
173411
+ }
173412
+ const deferredHistoryDrainEligible = historyWasConsumedThisPass && !suppressV12HistoryDrain;
173413
+ if (deferredHistoryDrainEligible) {
173414
+ args.deferredHistoryRefreshSessions.delete(args.sessionId);
173415
+ }
173416
+ if (explicitMaterializedSuccessfully || deferredMaterializedSuccessfully) {
173417
+ args.deferredMaterializationSessions.delete(args.sessionId);
173418
+ }
172491
173419
  if (args.fullFeatureMode && args.autoSearch?.enabled && args.projectPath) {
172492
173420
  const visibleMemoryIds = getVisibleMemoryIds(args.db, args.sessionId) ?? undefined;
172493
173421
  try {
@@ -172510,6 +173438,7 @@ async function runPostTransformPhase(args) {
172510
173438
  sessionLog(args.sessionId, "auto-search runner failed:", error51);
172511
173439
  }
172512
173440
  }
173441
+ return { explicitMaterializedSuccessfully, deferredMaterializedSuccessfully };
172513
173442
  }
172514
173443
 
172515
173444
  // src/hooks/magic-context/nudge-placement-store.ts
@@ -172583,6 +173512,8 @@ function findLastAssistantModel(messages) {
172583
173512
  function createTransform(deps) {
172584
173513
  const loadedSessions = new Set;
172585
173514
  const lastEmergencyNotificationCount = new Map;
173515
+ const deferredHistoryRefreshSessions = deps.deferredHistoryRefreshSessions ?? new Set;
173516
+ const deferredMaterializationSessions = deps.deferredMaterializationSessions ?? new Set;
172586
173517
  return async (_input, output) => {
172587
173518
  const startTime = performance.now();
172588
173519
  const messages = output.messages;
@@ -172691,7 +173622,17 @@ function createTransform(deps) {
172691
173622
  }
172692
173623
  const historyBudgetTokens = resolveHistoryBudgetTokens(deps.historyBudgetPercentage, contextUsageEarly, deps.executeThresholdPercentage, deps.getModelKey?.(sessionId), deps.executeThresholdTokens);
172693
173624
  const schedulerDecisionEarly = resolveSchedulerDecision(deps.scheduler, sessionMeta, contextUsageEarly, sessionId, deps.getModelKey?.(sessionId));
172694
- const isCacheBusting = deps.historyRefreshSessions.has(sessionId);
173625
+ const historyRefreshExplicitBeforePrepare = deps.historyRefreshSessions.has(sessionId);
173626
+ const earlyActiveRunBlocksMaterialization = (getActiveCompartmentRun(sessionId) !== undefined || sessionMeta.compartmentInProgress) && contextUsageEarly.percentage < FORCE_MATERIALIZE_PERCENTAGE;
173627
+ const canConsumeDeferredEarly = canConsumeDeferredOnThisPass({
173628
+ schedulerDecision: schedulerDecisionEarly,
173629
+ contextPercentage: contextUsageEarly.percentage,
173630
+ justAwaitedPublication: false,
173631
+ activeRunBlocksMaterialization: earlyActiveRunBlocksMaterialization
173632
+ });
173633
+ const consumingDeferredEarly = canConsumeDeferredEarly && deferredHistoryRefreshSessions.has(sessionId);
173634
+ const isCacheBusting = historyRefreshExplicitBeforePrepare || consumingDeferredEarly;
173635
+ const historyBustThisPass = isCacheBusting;
172695
173636
  if (historianFailureState.failureCount === 0) {
172696
173637
  lastEmergencyNotificationCount.delete(sessionId);
172697
173638
  }
@@ -172719,6 +173660,7 @@ function createTransform(deps) {
172719
173660
  historianChunkTokens: deps.getHistorianChunkTokens?.() ?? 20000,
172720
173661
  historyBudgetTokens,
172721
173662
  historianTimeoutMs: deps.historianTimeoutMs,
173663
+ fallbackModels: deps.fallbackModels,
172722
173664
  directory: compartmentDirectory,
172723
173665
  fallbackModelId,
172724
173666
  getNotificationParams: () => notificationParams,
@@ -172730,9 +173672,10 @@ function createTransform(deps) {
172730
173672
  compressorMaxMergeDepth: deps.compressorMaxMergeDepth,
172731
173673
  memoryEnabled: deps.memoryConfig?.enabled,
172732
173674
  autoPromote: deps.memoryConfig?.autoPromote,
172733
- onInjectionCacheCleared: (sid) => {
172734
- deps.historyRefreshSessions.add(sid);
172735
- deps.pendingMaterializationSessions.add(sid);
173675
+ preserveInjectionCacheUntilConsumed: true,
173676
+ onCompartmentStatePublished: (sid) => {
173677
+ deferredHistoryRefreshSessions.add(sid);
173678
+ deferredMaterializationSessions.add(sid);
172736
173679
  }
172737
173680
  });
172738
173681
  skipCompartmentAwaitForThisPass = true;
@@ -172765,11 +173708,15 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
172765
173708
  logTransformTiming(sessionId, "emergencyRecoveryBlock", tFirstPass);
172766
173709
  const projectIdentity = deps.memoryConfig?.enabled ? resolveProjectIdentity(compartmentDirectory || process.cwd()) : undefined;
172767
173710
  let pendingCompartmentInjection = null;
173711
+ let rebuiltHistoryFromInitialPrepare = false;
172768
173712
  if (fullFeatureMode) {
172769
173713
  const tInj = performance.now();
172770
173714
  pendingCompartmentInjection = prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, projectIdentity, deps.memoryConfig?.injectionBudgetTokens, deps.experimentalTemporalAwareness);
172771
173715
  logTransformTiming(sessionId, "prepareCompartmentInjection", tInj);
172772
173716
  if (isCacheBusting) {
173717
+ rebuiltHistoryFromInitialPrepare = true;
173718
+ }
173719
+ if (historyRefreshExplicitBeforePrepare) {
172773
173720
  deps.historyRefreshSessions.delete(sessionId);
172774
173721
  }
172775
173722
  }
@@ -172888,6 +173835,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
172888
173835
  historianChunkTokens: deps.getHistorianChunkTokens?.() ?? 20000,
172889
173836
  historyBudgetTokens,
172890
173837
  historianTimeoutMs: deps.historianTimeoutMs,
173838
+ fallbackModels: deps.fallbackModels,
172891
173839
  compartmentDirectory,
172892
173840
  messages,
172893
173841
  pendingCompartmentInjection,
@@ -172895,7 +173843,9 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
172895
173843
  projectPath: projectIdentity,
172896
173844
  injectionBudgetTokens: deps.memoryConfig?.injectionBudgetTokens,
172897
173845
  getNotificationParams: rawGetNotifParams ? () => rawGetNotifParams(sessionId) : undefined,
172898
- cacheAlreadyBusting: isCacheBusting || schedulerDecisionEarly === "execute",
173846
+ safeForBackgroundCompression: isCacheBusting || schedulerDecisionEarly === "execute",
173847
+ suppressBackgroundCompressionThisPass: historyBustThisPass,
173848
+ deferredHistoryRefreshSessions,
172899
173849
  skipAwaitForThisPass: skipCompartmentAwaitForThisPass,
172900
173850
  experimentalCompactionMarkers: deps.experimentalCompactionMarkers,
172901
173851
  experimentalUserMemories: deps.experimentalUserMemories,
@@ -172906,9 +173856,9 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
172906
173856
  compressorCooldownMs: deps.compressorCooldownMs,
172907
173857
  memoryEnabled: deps.memoryConfig?.enabled,
172908
173858
  autoPromote: deps.memoryConfig?.autoPromote,
172909
- onInjectionCacheCleared: (sid) => {
172910
- deps.historyRefreshSessions.add(sid);
172911
- deps.pendingMaterializationSessions.add(sid);
173859
+ onCompartmentStatePublished: (sid) => {
173860
+ deferredHistoryRefreshSessions.add(sid);
173861
+ deferredMaterializationSessions.add(sid);
172912
173862
  }
172913
173863
  });
172914
173864
  pendingCompartmentInjection = compartmentPhase.pendingCompartmentInjection;
@@ -172916,6 +173866,15 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
172916
173866
  const compartmentInProgress = compartmentPhase.compartmentInProgress;
172917
173867
  sessionMeta = { ...sessionMeta, compartmentInProgress };
172918
173868
  logTransformTiming(sessionId, "compartmentPhase", tCompartmentPhase);
173869
+ const lateActiveRunBlocksMaterialization = getActiveCompartmentRun(sessionId) !== undefined && contextUsageEarly.percentage < FORCE_MATERIALIZE_PERCENTAGE;
173870
+ const canConsumeDeferredLate = canConsumeDeferredOnThisPass({
173871
+ schedulerDecision: schedulerDecisionEarly,
173872
+ contextPercentage: contextUsageEarly.percentage,
173873
+ justAwaitedPublication: compartmentPhase.justAwaitedPublication,
173874
+ activeRunBlocksMaterialization: lateActiveRunBlocksMaterialization
173875
+ });
173876
+ const wasEmergencyBlock = contextUsageEarly.percentage >= FORCE_MATERIALIZE_PERCENTAGE && compartmentPhase.justAwaitedPublication;
173877
+ const historyRebuiltThisPass = wasEmergencyBlock ? compartmentPhase.rebuiltHistoryThisPass : rebuiltHistoryFromInitialPrepare || compartmentPhase.rebuiltHistoryThisPass;
172919
173878
  const tPostProcess = performance.now();
172920
173879
  await runPostTransformPhase({
172921
173880
  sessionId,
@@ -172931,10 +173890,18 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
172931
173890
  fullFeatureMode,
172932
173891
  canRunCompartments,
172933
173892
  awaitedCompartmentRun,
173893
+ phaseJustAwaitedPublication: compartmentPhase.justAwaitedPublication,
172934
173894
  compartmentInProgress,
173895
+ historyRefreshExplicitBeforePrepare,
173896
+ compartmentInjectionRebuiltFromDb: pendingCompartmentInjection?.rebuiltFromDb === true,
173897
+ rebuiltHistoryFromInitialPrepare,
173898
+ historyRebuiltThisPass,
173899
+ canConsumeDeferredLate,
172935
173900
  sessionMeta,
172936
173901
  currentTurnId,
172937
173902
  pendingMaterializationSessions: deps.pendingMaterializationSessions,
173903
+ deferredHistoryRefreshSessions,
173904
+ deferredMaterializationSessions,
172938
173905
  lastHeuristicsTurnId: deps.lastHeuristicsTurnId,
172939
173906
  autoDropToolAge: deps.autoDropToolAge,
172940
173907
  dropToolStructure: deps.dropToolStructure ?? true,
@@ -172948,6 +173915,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
172948
173915
  forceMaterializationPercentage: FORCE_MATERIALIZE_PERCENTAGE,
172949
173916
  hasRecentReduceCall,
172950
173917
  projectPath: deps.projectPath,
173918
+ sessionDirectory,
172951
173919
  autoSearch: deps.autoSearch,
172952
173920
  cavemanTextCompression: deps.ctxReduceEnabled === false && !reducedMode ? deps.cavemanTextCompression : undefined,
172953
173921
  liveProviderID
@@ -173355,6 +174323,15 @@ function createEventHandler2(deps) {
173355
174323
  } catch (error51) {
173356
174324
  sessionLog(sessionId, "event session.compacted marker cleanup failed:", error51);
173357
174325
  }
174326
+ try {
174327
+ const pending = getPendingCompactionMarkerState(deps.db, sessionId);
174328
+ if (pending) {
174329
+ clearPendingCompactionMarkerStateIf(deps.db, sessionId, pending);
174330
+ }
174331
+ } catch (error51) {
174332
+ sessionLog(sessionId, "event session.compacted pending-marker cleanup failed:", error51);
174333
+ }
174334
+ resetDegradedCacheCount(sessionId);
173358
174335
  clearMessageTokensCache(sessionId);
173359
174336
  deps.onSessionCacheInvalidated?.(sessionId);
173360
174337
  return;
@@ -173371,6 +174348,7 @@ function createEventHandler2(deps) {
173371
174348
  } catch (error51) {
173372
174349
  sessionLog(sessionId, "event session.deleted persistence failed:", error51);
173373
174350
  }
174351
+ resetDegradedCacheCount(sessionId);
173374
174352
  deps.onSessionCacheInvalidated?.(sessionId);
173375
174353
  deps.onSessionDeleted?.(sessionId);
173376
174354
  deps.contextUsageMap.delete(sessionId);
@@ -173579,6 +174557,10 @@ function applyStickySnapshotCache(sessionId, fresh) {
173579
174557
  cache.delete(sessionId);
173580
174558
  return fresh;
173581
174559
  }
174560
+ if (!hasInFlightEvidence(fresh)) {
174561
+ cache.delete(sessionId);
174562
+ return fresh;
174563
+ }
173582
174564
  return {
173583
174565
  ...fresh,
173584
174566
  usagePercentage: cached2.snapshot.usagePercentage,
@@ -173592,6 +174574,9 @@ function applyStickySnapshotCache(sessionId, fresh) {
173592
174574
  toolDefinitionTokens: cached2.snapshot.toolDefinitionTokens
173593
174575
  };
173594
174576
  }
174577
+ function hasInFlightEvidence(snapshot) {
174578
+ return snapshot.compartmentInProgress || snapshot.historianRunning || snapshot.pendingOpsCount > 0;
174579
+ }
173595
174580
  function clearSidebarSnapshotCache(sessionId) {
173596
174581
  cache.delete(sessionId);
173597
174582
  }
@@ -173692,8 +174677,10 @@ function createEventHook(args) {
173692
174677
  args.recentReduceBySession.delete(sessionId);
173693
174678
  args.toolUsageSinceUserTurn.delete(sessionId);
173694
174679
  args.historyRefreshSessions.delete(sessionId);
174680
+ args.deferredHistoryRefreshSessions.delete(sessionId);
173695
174681
  args.systemPromptRefreshSessions.delete(sessionId);
173696
174682
  args.pendingMaterializationSessions.delete(sessionId);
174683
+ args.deferredMaterializationSessions.delete(sessionId);
173697
174684
  args.lastHeuristicsTurnId.delete(sessionId);
173698
174685
  args.commitSeenLastPass?.delete(sessionId);
173699
174686
  clearNoteNudgeState(args.db, sessionId);
@@ -173728,9 +174715,17 @@ function createToolExecuteAfterHook(args) {
173728
174715
  if (typedInput.tool === "todowrite") {
173729
174716
  const todoArgs = typedInput.args;
173730
174717
  const todos = todoArgs?.todos;
173731
- if (Array.isArray(todos) && todos.length > 0 && todos.every((t) => t.status === "completed" || t.status === "cancelled")) {
173732
- const sessionMeta = getOrCreateSessionMeta(args.db, typedInput.sessionID);
173733
- if (!sessionMeta.isSubagent) {
174718
+ const sessionMeta = Array.isArray(todos) ? getOrCreateSessionMeta(args.db, typedInput.sessionID) : null;
174719
+ if (sessionMeta && !sessionMeta.isSubagent) {
174720
+ const normalizedTodos = normalizeTodoStateJson(todos);
174721
+ if (normalizedTodos !== null) {
174722
+ updateSessionMeta(args.db, typedInput.sessionID, {
174723
+ lastTodoState: normalizedTodos
174724
+ });
174725
+ }
174726
+ }
174727
+ if (Array.isArray(todos) && todos.length > 0 && todos.every((t) => typeof t === "object" && t !== null && (t.status === "completed" || t.status === "cancelled"))) {
174728
+ if (sessionMeta && !sessionMeta.isSubagent) {
173734
174729
  onNoteTrigger(args.db, typedInput.sessionID, "todos_complete");
173735
174730
  }
173736
174731
  }
@@ -173746,9 +174741,9 @@ function createToolExecuteAfterHook(args) {
173746
174741
  init_send_session_notification();
173747
174742
 
173748
174743
  // src/hooks/magic-context/system-prompt-hash.ts
173749
- import { createHash as createHash4 } from "node:crypto";
174744
+ import { createHash as createHash6 } from "node:crypto";
173750
174745
  import { existsSync as existsSync13, readFileSync as readFileSync9, realpathSync } from "node:fs";
173751
- import { join as join22, resolve as resolve4, sep } from "node:path";
174746
+ import { join as join21, resolve as resolve4, sep } from "node:path";
173752
174747
 
173753
174748
  // src/agents/magic-context-prompt.ts
173754
174749
  function getToolHistoryGuidance(dropToolStructure) {
@@ -173861,7 +174856,7 @@ var DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
173861
174856
  function readProjectDocs(directory) {
173862
174857
  const sections = [];
173863
174858
  for (const filename of DOC_FILES) {
173864
- const filePath = join22(directory, filename);
174859
+ const filePath = join21(directory, filename);
173865
174860
  try {
173866
174861
  if (existsSync13(filePath)) {
173867
174862
  const content = readFileSync9(filePath, "utf-8").trim();
@@ -174051,7 +175046,7 @@ ${sections.join(`
174051
175046
  `);
174052
175047
  if (systemContent.length === 0)
174053
175048
  return;
174054
- const currentHash = createHash4("md5").update(systemContent).digest("hex");
175049
+ const currentHash = createHash6("md5").update(systemContent).digest("hex");
174055
175050
  if (!sessionMetaEarly) {
174056
175051
  return;
174057
175052
  }
@@ -174066,14 +175061,12 @@ ${sections.join(`
174066
175061
  } else if (previousHash === "" || previousHash === "0") {
174067
175062
  sessionLog(sessionId, `system prompt hash initialized: ${currentHash} (len=${systemContent.length})`);
174068
175063
  }
174069
- const systemPromptTokens = estimateTokens(systemContent);
174070
175064
  if (currentHash !== previousHash) {
175065
+ const systemPromptTokens = estimateTokens(systemContent);
174071
175066
  updateSessionMeta(deps.db, sessionId, {
174072
175067
  systemPromptHash: currentHash,
174073
175068
  systemPromptTokens
174074
175069
  });
174075
- } else if (Math.abs(sessionMeta.systemPromptTokens - systemPromptTokens) > 50) {
174076
- updateSessionMeta(deps.db, sessionId, { systemPromptTokens });
174077
175070
  }
174078
175071
  if (isCacheBusting) {
174079
175072
  deps.systemPromptRefreshSessions.delete(sessionId);
@@ -174135,10 +175128,24 @@ function createMagicContextHook(deps) {
174135
175128
  }
174136
175129
  let lastScheduleCheckMs = 0;
174137
175130
  const getHistorianChunkTokens = () => deriveHistorianChunkTokens(resolveHistorianContextLimit(deps.config.historian?.model));
175131
+ const historianFallbackModels = resolveFallbackChain(HISTORIAN_AGENT, deps.config.historian?.fallback_models);
174138
175132
  const nudgePlacements = createNudgePlacementStore(db);
174139
175133
  const historyRefreshSessions = deps.liveSessionState?.historyRefreshSessions ?? new Set;
175134
+ const deferredHistoryRefreshSessions = deps.liveSessionState?.deferredHistoryRefreshSessions ?? new Set;
175135
+ try {
175136
+ const sessionsWithPending = getSessionsWithPendingMarker(db);
175137
+ if (sessionsWithPending.length > 0) {
175138
+ for (const sid of sessionsWithPending) {
175139
+ deferredHistoryRefreshSessions.add(sid);
175140
+ }
175141
+ log(`[magic-context] rehydrated ${sessionsWithPending.length} session(s) with pending compaction-marker drain at hook init`);
175142
+ }
175143
+ } catch (error51) {
175144
+ log("[magic-context] hook init: pending-marker rehydration failed:", error51);
175145
+ }
174140
175146
  const systemPromptRefreshSessions = deps.liveSessionState?.systemPromptRefreshSessions ?? new Set;
174141
175147
  const pendingMaterializationSessions = deps.liveSessionState?.pendingMaterializationSessions ?? new Set;
175148
+ const deferredMaterializationSessions = deps.liveSessionState?.deferredMaterializationSessions ?? new Set;
174142
175149
  const lastHeuristicsTurnId = new Map;
174143
175150
  const commitSeenLastPass = new Map;
174144
175151
  const variantBySession = deps.liveSessionState?.variantBySession ?? new Map;
@@ -174179,7 +175186,9 @@ function createMagicContextHook(deps) {
174179
175186
  dropToolStructure: deps.config.drop_tool_structure ?? true,
174180
175187
  clearReasoningAge: deps.config.clear_reasoning_age ?? 50,
174181
175188
  historyRefreshSessions,
175189
+ deferredHistoryRefreshSessions,
174182
175190
  pendingMaterializationSessions,
175191
+ deferredMaterializationSessions,
174183
175192
  lastHeuristicsTurnId,
174184
175193
  commitSeenLastPass,
174185
175194
  client: deps.client,
@@ -174194,6 +175203,7 @@ function createMagicContextHook(deps) {
174194
175203
  executeThresholdPercentage: deps.config.execute_threshold_percentage,
174195
175204
  executeThresholdTokens: deps.config.execute_threshold_tokens,
174196
175205
  historianTimeoutMs: deps.config.historian_timeout_ms ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
175206
+ fallbackModels: historianFallbackModels,
174197
175207
  getNotificationParams: (sessionId) => getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession),
174198
175208
  getModelKey: (sessionId) => {
174199
175209
  const model = liveModelBySession.get(sessionId);
@@ -174251,7 +175261,7 @@ function createMagicContextHook(deps) {
174251
175261
  return;
174252
175262
  }
174253
175263
  try {
174254
- checkScheduleAndEnqueue(db, dreaming.schedule);
175264
+ checkScheduleAndEnqueue(db, dreaming.schedule, projectPath);
174255
175265
  lastScheduleCheckMs = now;
174256
175266
  } catch (error51) {
174257
175267
  log("[dreamer] scheduled enqueue check failed:", error51);
@@ -174271,7 +175281,9 @@ function createMagicContextHook(deps) {
174271
175281
  enabled: true,
174272
175282
  token_budget: deps.config.dreamer.pin_key_files.token_budget,
174273
175283
  min_reads: deps.config.dreamer.pin_key_files.min_reads
174274
- } : undefined
175284
+ } : undefined,
175285
+ fallbackModels: resolveFallbackChain(DREAMER_AGENT, deps.config.dreamer?.fallback_models),
175286
+ projectIdentity: projectPath
174275
175287
  }).catch((error51) => {
174276
175288
  log("[dreamer] scheduled queue processing failed:", error51);
174277
175289
  });
@@ -174305,6 +175317,7 @@ function createMagicContextHook(deps) {
174305
175317
  sessionId,
174306
175318
  historianChunkTokens: getHistorianChunkTokens(),
174307
175319
  historianTimeoutMs: deps.config.historian_timeout_ms ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
175320
+ fallbackModels: historianFallbackModels,
174308
175321
  directory: deps.directory,
174309
175322
  fallbackModelId: (() => {
174310
175323
  const model = resolveLiveModel(sessionId);
@@ -174314,9 +175327,12 @@ function createMagicContextHook(deps) {
174314
175327
  historianTwoPass: deps.config.historian?.two_pass === true,
174315
175328
  memoryEnabled: deps.config.memory?.enabled,
174316
175329
  autoPromote: deps.config.memory?.auto_promote ?? true,
174317
- onInjectionCacheCleared: (sid) => {
175330
+ onCompartmentStatePublished: (sid) => {
174318
175331
  historyRefreshSessions.add(sid);
174319
175332
  pendingMaterializationSessions.add(sid);
175333
+ },
175334
+ onDeferredMarkerPending: (sid) => {
175335
+ deferredHistoryRefreshSessions.add(sid);
174320
175336
  }
174321
175337
  }, options),
174322
175338
  sendNotification: async (sessionId, text, params) => {
@@ -174344,7 +175360,8 @@ function createMagicContextHook(deps) {
174344
175360
  enabled: true,
174345
175361
  token_budget: deps.config.dreamer.pin_key_files.token_budget,
174346
175362
  min_reads: deps.config.dreamer.pin_key_files.min_reads
174347
- } : undefined
175363
+ } : undefined,
175364
+ fallbackModels: resolveFallbackChain(DREAMER_AGENT, deps.config.dreamer.fallback_models)
174348
175365
  } : undefined
174349
175366
  });
174350
175367
  const systemPromptHash = createSystemPromptHashHandler({
@@ -174381,8 +175398,10 @@ function createMagicContextHook(deps) {
174381
175398
  recentReduceBySession,
174382
175399
  toolUsageSinceUserTurn,
174383
175400
  historyRefreshSessions,
175401
+ deferredHistoryRefreshSessions,
174384
175402
  systemPromptRefreshSessions,
174385
175403
  pendingMaterializationSessions,
175404
+ deferredMaterializationSessions,
174386
175405
  lastHeuristicsTurnId,
174387
175406
  commitSeenLastPass,
174388
175407
  client: deps.client,
@@ -175006,19 +176025,7 @@ function buildStatusDetail(db, sessionId, directory, modelKey, config2, liveSess
175006
176025
  }
175007
176026
  detail.nextNudgeAfter = detail.lastNudgeTokens + detail.nudgeInterval;
175008
176027
  try {
175009
- const compartments = db.prepare("SELECT content, title, start_message, end_message FROM compartments WHERE session_id = ?").all(sessionId);
175010
- const facts = db.prepare("SELECT content FROM session_facts WHERE session_id = ?").all(sessionId);
175011
- let histTokens = 0;
175012
- for (const c of compartments) {
175013
- histTokens += estimateTokens(`<compartment start="${c.start_message}" end="${c.end_message}" title="${c.title}">
175014
- ${c.content}
175015
- </compartment>
175016
- `);
175017
- }
175018
- for (const f of facts) {
175019
- histTokens += estimateTokens(`* ${f.content}
175020
- `);
175021
- }
176028
+ const histTokens = base.compartmentTokens + base.factTokens;
175022
176029
  detail.historyBlockTokens = histTokens;
175023
176030
  if (detail.contextLimit > 0) {
175024
176031
  const budget = Math.floor(detail.contextLimit * (Math.min(detail.executeThreshold, 80) / 100) * detail.historyBudgetPercentage);
@@ -175088,7 +176095,7 @@ function registerRpcHandlers(rpcServer, args) {
175088
176095
  memoryEnabled: config2.memory?.enabled,
175089
176096
  autoPromote: config2.memory?.auto_promote ?? true,
175090
176097
  getNotificationParams: () => getNotificationParams(sessionId),
175091
- onInjectionCacheCleared: (sid) => {
176098
+ onCompartmentStatePublished: (sid) => {
175092
176099
  liveSessionState.historyRefreshSessions.add(sid);
175093
176100
  liveSessionState.pendingMaterializationSessions.add(sid);
175094
176101
  }
@@ -175099,8 +176106,9 @@ function registerRpcHandlers(rpcServer, args) {
175099
176106
  });
175100
176107
  return { ok: true };
175101
176108
  });
175102
- rpcServer.handle("pending-notifications", async () => {
175103
- const notifications = drainNotifications();
176109
+ rpcServer.handle("pending-notifications", async (params) => {
176110
+ const lastReceivedId = Number(params.lastReceivedId ?? 0);
176111
+ const notifications = drainNotifications(Number.isFinite(lastReceivedId) ? lastReceivedId : 0);
175104
176112
  return { messages: notifications };
175105
176113
  });
175106
176114
  }
@@ -175191,7 +176199,7 @@ function normalizeLimit2(limit) {
175191
176199
  return Math.max(1, Math.floor(limit));
175192
176200
  }
175193
176201
  function getAllowedActions(deps) {
175194
- const allowed = deps.allowedActions?.length ? deps.allowedActions : [...CTX_MEMORY_DREAMER_ACTIONS];
176202
+ const allowed = deps.allowedActions?.length ? deps.allowedActions : ["write", "delete"];
175195
176203
  return [...allowed];
175196
176204
  }
175197
176205
  function normalizeCategory(category) {
@@ -175297,7 +176305,7 @@ function createCtxMemoryTool(deps) {
175297
176305
  return tool2({
175298
176306
  description: CTX_MEMORY_DESCRIPTION,
175299
176307
  args: {
175300
- action: tool2.schema.enum(allowedActions).describe("Action to perform on memories"),
176308
+ action: tool2.schema.enum([...CTX_MEMORY_DREAMER_ACTIONS]).describe("Action to perform on memories"),
175301
176309
  content: tool2.schema.string().optional().describe("Memory content (required for write, update, merge)"),
175302
176310
  category: tool2.schema.string().optional().describe("Memory category (required for write, optional filter for list, optional override for merge)"),
175303
176311
  id: tool2.schema.number().optional().describe("Memory ID (required for delete, update, archive)"),
@@ -176058,19 +177066,68 @@ init_models_dev_cache();
176058
177066
 
176059
177067
  // src/shared/rpc-server.ts
176060
177068
  init_logger();
176061
- import { mkdirSync as mkdirSync7, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync7 } from "node:fs";
177069
+ import {
177070
+ mkdirSync as mkdirSync8,
177071
+ readdirSync,
177072
+ readFileSync as readFileSync10,
177073
+ renameSync as renameSync2,
177074
+ unlinkSync as unlinkSync3,
177075
+ writeFileSync as writeFileSync7
177076
+ } from "node:fs";
176062
177077
  import { createServer } from "node:http";
176063
- import { dirname as dirname6 } from "node:path";
177078
+ import { dirname as dirname7 } from "node:path";
176064
177079
 
176065
177080
  // src/shared/rpc-utils.ts
176066
- import { createHash as createHash5 } from "node:crypto";
176067
- import { join as join23 } from "node:path";
177081
+ import { createHash as createHash7 } from "node:crypto";
177082
+ import { join as join22 } from "node:path";
176068
177083
  function projectHash(directory) {
176069
177084
  const normalized = directory.replace(/\/+$/, "");
176070
- return createHash5("sha256").update(normalized).digest("hex").slice(0, 16);
177085
+ return createHash7("sha256").update(normalized).digest("hex").slice(0, 16);
176071
177086
  }
176072
- function rpcPortFilePath(storageDir, directory) {
176073
- return join23(storageDir, "rpc", projectHash(directory), "port");
177087
+ function rpcPortDir(storageDir, directory) {
177088
+ return join22(storageDir, "rpc", projectHash(directory));
177089
+ }
177090
+ function rpcPortFilePath(storageDir, directory, pid = process.pid) {
177091
+ return join22(rpcPortDir(storageDir, directory), `port-${pid}.json`);
177092
+ }
177093
+ function isPidAlive(pid) {
177094
+ if (!Number.isInteger(pid) || pid <= 0)
177095
+ return false;
177096
+ try {
177097
+ process.kill(pid, 0);
177098
+ return true;
177099
+ } catch (err) {
177100
+ return err.code === "EPERM";
177101
+ }
177102
+ }
177103
+ function parseRpcPortFile(content, fallbackPid = 0) {
177104
+ const trimmed = content.trim();
177105
+ if (!trimmed)
177106
+ return null;
177107
+ if (trimmed.startsWith("{")) {
177108
+ try {
177109
+ const parsed = JSON.parse(trimmed);
177110
+ const port2 = Number(parsed.port);
177111
+ const pid = Number(parsed.pid);
177112
+ const startedAt = Number(parsed.started_at);
177113
+ if (!isValidPort(port2) || !Number.isInteger(pid) || pid <= 0)
177114
+ return null;
177115
+ return {
177116
+ port: port2,
177117
+ pid,
177118
+ started_at: Number.isFinite(startedAt) ? startedAt : 0
177119
+ };
177120
+ } catch {
177121
+ return null;
177122
+ }
177123
+ }
177124
+ const port = Number.parseInt(trimmed, 10);
177125
+ if (!isValidPort(port))
177126
+ return null;
177127
+ return { port, pid: fallbackPid, started_at: 0 };
177128
+ }
177129
+ function isValidPort(port) {
177130
+ return Number.isInteger(port) && port > 0 && port <= 65535;
176074
177131
  }
176075
177132
 
176076
177133
  // src/shared/rpc-server.ts
@@ -176079,8 +177136,11 @@ class MagicContextRpcServer {
176079
177136
  port = 0;
176080
177137
  handlers = new Map;
176081
177138
  portFilePath;
177139
+ portDir;
177140
+ startedAt = Date.now();
176082
177141
  constructor(storageDir, directory) {
176083
177142
  this.portFilePath = rpcPortFilePath(storageDir, directory);
177143
+ this.portDir = rpcPortDir(storageDir, directory);
176084
177144
  }
176085
177145
  handle(method, handler) {
176086
177146
  this.handlers.set(method, handler);
@@ -176101,10 +177161,15 @@ class MagicContextRpcServer {
176101
177161
  this.port = addr.port;
176102
177162
  this.server = server;
176103
177163
  try {
176104
- const dir = dirname6(this.portFilePath);
176105
- mkdirSync7(dir, { recursive: true });
177164
+ this.warnIfOtherLiveInstance();
177165
+ const dir = dirname7(this.portFilePath);
177166
+ mkdirSync8(dir, { recursive: true });
176106
177167
  const tmpPath = `${this.portFilePath}.tmp`;
176107
- writeFileSync7(tmpPath, String(this.port), "utf-8");
177168
+ writeFileSync7(tmpPath, JSON.stringify({
177169
+ port: this.port,
177170
+ pid: process.pid,
177171
+ started_at: this.startedAt
177172
+ }), "utf-8");
176108
177173
  renameSync2(tmpPath, this.portFilePath);
176109
177174
  log(`[rpc] server listening on 127.0.0.1:${this.port}`);
176110
177175
  } catch (err) {
@@ -176115,6 +177180,19 @@ class MagicContextRpcServer {
176115
177180
  server.unref();
176116
177181
  });
176117
177182
  }
177183
+ warnIfOtherLiveInstance() {
177184
+ try {
177185
+ for (const entry of readdirSync(this.portDir)) {
177186
+ if (!entry.startsWith("port-") || !entry.endsWith(".json"))
177187
+ continue;
177188
+ const record2 = parseRpcPortFile(readFileSync10(`${this.portDir}/${entry}`, "utf-8"));
177189
+ if (!record2 || record2.pid === process.pid || !isPidAlive(record2.pid))
177190
+ continue;
177191
+ log(`[rpc] another Magic Context RPC server is active for this project (pid ${record2.pid}, port ${record2.port}); starting separate instance on a new port`);
177192
+ return;
177193
+ }
177194
+ } catch {}
177195
+ }
176118
177196
  stop() {
176119
177197
  if (this.server) {
176120
177198
  this.server.close();