@cortexkit/opencode-magic-context 0.9.1 → 0.10.1

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 (33) hide show
  1. package/dist/cli/doctor.d.ts.map +1 -1
  2. package/dist/cli.js +6 -3
  3. package/dist/config/schema/magic-context.d.ts +0 -4
  4. package/dist/config/schema/magic-context.d.ts.map +1 -1
  5. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  6. package/dist/hooks/magic-context/compartment-runner-types.d.ts +6 -1
  7. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  8. package/dist/hooks/magic-context/compartment-trigger.d.ts +1 -1
  9. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  10. package/dist/hooks/magic-context/derive-budgets.d.ts +57 -0
  11. package/dist/hooks/magic-context/derive-budgets.d.ts.map +1 -0
  12. package/dist/hooks/magic-context/event-handler.d.ts +0 -1
  13. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  14. package/dist/hooks/magic-context/event-resolvers.d.ts +1 -3
  15. package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
  16. package/dist/hooks/magic-context/hook.d.ts +3 -2
  17. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  18. package/dist/hooks/magic-context/nudge-injection.d.ts.map +1 -1
  19. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +1 -1
  20. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  21. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  22. package/dist/hooks/magic-context/transform.d.ts +7 -1
  23. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +347 -157
  26. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  27. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  28. package/dist/plugin/tui-action-consumer.d.ts.map +1 -1
  29. package/dist/shared/models-dev-cache.d.ts +52 -4
  30. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  31. package/package.json +2 -2
  32. package/src/shared/models-dev-cache.test.ts +228 -0
  33. package/src/shared/models-dev-cache.ts +146 -28
package/dist/index.js CHANGED
@@ -14070,7 +14070,7 @@ var init_agent_overrides = __esm(() => {
14070
14070
  });
14071
14071
 
14072
14072
  // src/config/schema/magic-context.ts
14073
- var DEFAULT_NUDGE_INTERVAL_TOKENS = 1e4, DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65, DEFAULT_COMPARTMENT_TOKEN_BUDGET = 20000, DEFAULT_HISTORIAN_TIMEOUT_MS = 300000, DEFAULT_HISTORY_BUDGET_PERCENTAGE = 0.15, DEFAULT_LOCAL_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2", DREAMER_TASKS, DreamingTaskSchema, DEFAULT_DREAMER_TASKS, DreamerConfigSchema, SidekickConfigSchema, BaseEmbeddingConfigSchema, EmbeddingConfigSchema, MagicContextConfigSchema;
14073
+ var DEFAULT_NUDGE_INTERVAL_TOKENS = 1e4, DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65, DEFAULT_HISTORIAN_TIMEOUT_MS = 300000, DEFAULT_HISTORY_BUDGET_PERCENTAGE = 0.15, DEFAULT_LOCAL_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2", DREAMER_TASKS, DreamingTaskSchema, DEFAULT_DREAMER_TASKS, DreamerConfigSchema, SidekickConfigSchema, BaseEmbeddingConfigSchema, EmbeddingConfigSchema, MagicContextConfigSchema;
14074
14074
  var init_magic_context = __esm(() => {
14075
14075
  init_zod();
14076
14076
  init_agent_overrides();
@@ -14156,7 +14156,6 @@ var init_magic_context = __esm(() => {
14156
14156
  drop_tool_structure: exports_external.boolean().default(true),
14157
14157
  clear_reasoning_age: exports_external.number().min(10).default(50),
14158
14158
  iteration_nudge_threshold: exports_external.number().min(5).default(15),
14159
- compartment_token_budget: exports_external.number().min(1e4).default(DEFAULT_COMPARTMENT_TOKEN_BUDGET),
14160
14159
  history_budget_percentage: exports_external.number().min(0.05).max(0.5).default(DEFAULT_HISTORY_BUDGET_PERCENTAGE),
14161
14160
  historian_timeout_ms: exports_external.number().min(60000).default(DEFAULT_HISTORIAN_TIMEOUT_MS),
14162
14161
  commit_cluster_trigger: exports_external.object({
@@ -16166,7 +16165,57 @@ function cosineSimilarity(a, b) {
16166
16165
 
16167
16166
  // src/features/magic-context/memory/embedding-local.ts
16168
16167
  import { mkdirSync } from "fs";
16168
+ import { open, stat, unlink, writeFile } from "fs/promises";
16169
16169
  import { join as join9 } from "path";
16170
+ async function acquireModelLoadLock(lockPath) {
16171
+ const waitStart = Date.now();
16172
+ while (true) {
16173
+ try {
16174
+ const handle = await open(lockPath, "wx");
16175
+ try {
16176
+ await handle.writeFile(`pid=${process.pid} started=${Date.now()}
16177
+ `);
16178
+ } catch {}
16179
+ await handle.close();
16180
+ return async () => {
16181
+ try {
16182
+ await unlink(lockPath);
16183
+ } catch {}
16184
+ };
16185
+ } catch (error48) {
16186
+ const code = error48.code;
16187
+ if (code !== "EEXIST" && code !== "EPERM") {
16188
+ throw error48;
16189
+ }
16190
+ try {
16191
+ const info = await stat(lockPath);
16192
+ if (Date.now() - info.mtimeMs > STALE_LOCK_MS) {
16193
+ log(`[magic-context] embedding-load lock stale (>${STALE_LOCK_MS}ms), taking over`);
16194
+ try {
16195
+ await unlink(lockPath);
16196
+ } catch {}
16197
+ continue;
16198
+ }
16199
+ } catch {
16200
+ continue;
16201
+ }
16202
+ if (Date.now() - waitStart > MAX_LOCK_WAIT_MS) {
16203
+ log("[magic-context] embedding-load lock wait exceeded, proceeding without lock");
16204
+ return async () => {};
16205
+ }
16206
+ await new Promise((resolve2) => setTimeout(resolve2, LOCK_POLL_MS));
16207
+ }
16208
+ }
16209
+ }
16210
+ function startLockHeartbeat(lockPath) {
16211
+ const HEARTBEAT_MS = Math.floor(STALE_LOCK_MS / 3);
16212
+ const timer = setInterval(() => {
16213
+ writeFile(lockPath, `pid=${process.pid} alive=${Date.now()}
16214
+ `).catch(() => {});
16215
+ }, HEARTBEAT_MS);
16216
+ timer.unref?.();
16217
+ return () => clearInterval(timer);
16218
+ }
16170
16219
  async function withQuietConsole(fn) {
16171
16220
  const origWarn = console.warn;
16172
16221
  const origError = console.error;
@@ -16259,30 +16308,37 @@ class LocalEmbeddingProvider {
16259
16308
  log("[magic-context] could not create model cache dir, using library default");
16260
16309
  }
16261
16310
  const createPipeline = transformersModule.pipeline;
16262
- const MAX_ATTEMPTS = 3;
16263
- let lastError;
16264
- for (let attempt = 1;attempt <= MAX_ATTEMPTS; attempt++) {
16265
- try {
16266
- this.pipeline = await withQuietConsole(() => createPipeline("feature-extraction", this.model, {
16267
- quantized: true,
16268
- dtype: "fp32"
16269
- }));
16270
- lastError = undefined;
16271
- break;
16272
- } catch (error48) {
16273
- lastError = error48;
16274
- if (!isTransientLoadError(error48) || attempt === MAX_ATTEMPTS) {
16311
+ const lockPath = join9(modelCacheDir, ".load.lock");
16312
+ const releaseLock = await acquireModelLoadLock(lockPath);
16313
+ const stopHeartbeat = startLockHeartbeat(lockPath);
16314
+ try {
16315
+ const MAX_ATTEMPTS = 3;
16316
+ let lastError;
16317
+ for (let attempt = 1;attempt <= MAX_ATTEMPTS; attempt++) {
16318
+ try {
16319
+ this.pipeline = await withQuietConsole(() => createPipeline("feature-extraction", this.model, {
16320
+ dtype: "fp32"
16321
+ }));
16322
+ lastError = undefined;
16275
16323
  break;
16324
+ } catch (error48) {
16325
+ lastError = error48;
16326
+ if (!isTransientLoadError(error48) || attempt === MAX_ATTEMPTS) {
16327
+ break;
16328
+ }
16329
+ const delayMs = 300 * attempt + Math.floor(Math.random() * 200);
16330
+ log(`[magic-context] embedding model load attempt ${attempt}/${MAX_ATTEMPTS} failed transiently, retrying in ${delayMs}ms`);
16331
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
16276
16332
  }
16277
- const delayMs = 300 * attempt + Math.floor(Math.random() * 200);
16278
- log(`[magic-context] embedding model load attempt ${attempt}/${MAX_ATTEMPTS} failed transiently, retrying in ${delayMs}ms`);
16279
- await new Promise((resolve2) => setTimeout(resolve2, delayMs));
16280
16333
  }
16281
- }
16282
- if (this.pipeline) {
16283
- log(`[magic-context] embedding model loaded: ${this.model}`);
16284
- } else {
16285
- throw lastError ?? new Error("unknown embedding load failure");
16334
+ if (this.pipeline) {
16335
+ log(`[magic-context] embedding model loaded: ${this.model}`);
16336
+ } else {
16337
+ throw lastError ?? new Error("unknown embedding load failure");
16338
+ }
16339
+ } finally {
16340
+ stopHeartbeat();
16341
+ await releaseLock();
16286
16342
  }
16287
16343
  } catch (error48) {
16288
16344
  log("[magic-context] embedding model failed to load:", error48);
@@ -16357,10 +16413,13 @@ class LocalEmbeddingProvider {
16357
16413
  return this.pipeline !== null;
16358
16414
  }
16359
16415
  }
16416
+ var LOCK_POLL_MS = 150, STALE_LOCK_MS, MAX_LOCK_WAIT_MS;
16360
16417
  var init_embedding_local = __esm(() => {
16361
16418
  init_magic_context();
16362
16419
  init_data_path();
16363
16420
  init_logger();
16421
+ STALE_LOCK_MS = 3 * 60000;
16422
+ MAX_LOCK_WAIT_MS = 5 * 60000;
16364
16423
  });
16365
16424
 
16366
16425
  // src/features/magic-context/memory/embedding-openai.ts
@@ -18374,6 +18433,145 @@ var init_storage = __esm(() => {
18374
18433
  init_storage_tags();
18375
18434
  });
18376
18435
 
18436
+ // src/shared/models-dev-cache.ts
18437
+ import { createHash } from "crypto";
18438
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
18439
+ import { homedir as homedir5, platform as platform2 } from "os";
18440
+ import { join as join11 } from "path";
18441
+ function hashFast(input) {
18442
+ return createHash("sha1").update(input).digest("hex");
18443
+ }
18444
+ function getModelsJsonPath() {
18445
+ const explicit = process.env.OPENCODE_MODELS_PATH?.trim();
18446
+ if (explicit)
18447
+ return explicit;
18448
+ const xdgCache = process.env.XDG_CACHE_HOME;
18449
+ const os3 = platform2();
18450
+ let cacheBase;
18451
+ if (xdgCache) {
18452
+ cacheBase = xdgCache;
18453
+ } else if (os3 === "win32") {
18454
+ cacheBase = process.env.LOCALAPPDATA ?? join11(homedir5(), "AppData", "Local");
18455
+ } else {
18456
+ cacheBase = join11(homedir5(), ".cache");
18457
+ }
18458
+ const source = process.env.OPENCODE_MODELS_URL?.trim();
18459
+ const filename = source && source !== "https://models.dev" ? `models-${hashFast(source)}.json` : "models.json";
18460
+ return join11(cacheBase, "opencode", filename);
18461
+ }
18462
+ function getOpencodeConfigPath() {
18463
+ const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
18464
+ const configDir = envDir ? envDir : platform2() === "win32" ? join11(homedir5(), ".config", "opencode") : join11(process.env.XDG_CONFIG_HOME || join11(homedir5(), ".config"), "opencode");
18465
+ const jsonc = join11(configDir, "opencode.jsonc");
18466
+ if (existsSync5(jsonc))
18467
+ return jsonc;
18468
+ const json2 = join11(configDir, "opencode.json");
18469
+ if (existsSync5(json2))
18470
+ return json2;
18471
+ return null;
18472
+ }
18473
+ function loadModelsDevLimitsFromFile() {
18474
+ const limits = new Map;
18475
+ const modelsJsonPath = getModelsJsonPath();
18476
+ let fileFound = false;
18477
+ try {
18478
+ if (existsSync5(modelsJsonPath)) {
18479
+ fileFound = true;
18480
+ const raw = readFileSync4(modelsJsonPath, "utf-8");
18481
+ const data = JSON.parse(raw);
18482
+ for (const [providerId, provider2] of Object.entries(data)) {
18483
+ if (!provider2?.models || typeof provider2.models !== "object")
18484
+ continue;
18485
+ for (const [modelId, model] of Object.entries(provider2.models)) {
18486
+ const context = model?.limit?.context;
18487
+ if (typeof context === "number" && context > 0) {
18488
+ limits.set(`${providerId}/${modelId}`, context);
18489
+ const modes = model?.experimental?.modes;
18490
+ if (modes && typeof modes === "object") {
18491
+ for (const mode of Object.keys(modes)) {
18492
+ limits.set(`${providerId}/${modelId}-${mode}`, context);
18493
+ }
18494
+ }
18495
+ }
18496
+ }
18497
+ }
18498
+ }
18499
+ } catch (error48) {
18500
+ sessionLog("global", `models-dev-cache: failed to read models.json at ${modelsJsonPath}:`, error48 instanceof Error ? error48.message : String(error48));
18501
+ }
18502
+ try {
18503
+ const configPath = getOpencodeConfigPath();
18504
+ if (configPath && existsSync5(configPath)) {
18505
+ let raw = readFileSync4(configPath, "utf-8");
18506
+ raw = raw.replace(/"(?:[^"\\]|\\.)*"|\/\/.*$/gm, (match) => match.startsWith('"') ? match : "");
18507
+ const config2 = JSON.parse(raw);
18508
+ if (config2.provider && typeof config2.provider === "object") {
18509
+ for (const [providerId, provider2] of Object.entries(config2.provider)) {
18510
+ if (!provider2?.models || typeof provider2.models !== "object")
18511
+ continue;
18512
+ for (const [modelId, model] of Object.entries(provider2.models)) {
18513
+ const context = model?.limit?.context;
18514
+ if (typeof context === "number" && context > 0) {
18515
+ limits.set(`${providerId}/${modelId}`, context);
18516
+ }
18517
+ }
18518
+ }
18519
+ }
18520
+ }
18521
+ } catch (error48) {
18522
+ sessionLog("global", "models-dev-cache: failed to read opencode config for custom models:", error48 instanceof Error ? error48.message : String(error48));
18523
+ }
18524
+ sessionLog("global", `models-dev-cache: file-layer loaded ${limits.size} model limits (modelsJsonPath=${modelsJsonPath}, found=${fileFound})`);
18525
+ return limits;
18526
+ }
18527
+ async function refreshModelLimitsFromApi(client) {
18528
+ try {
18529
+ const result = await client.config.providers();
18530
+ const data = result.data;
18531
+ const providers = data?.providers;
18532
+ if (!Array.isArray(providers)) {
18533
+ sessionLog("global", "models-dev-cache: API refresh returned no providers payload");
18534
+ return;
18535
+ }
18536
+ const map2 = new Map;
18537
+ for (const entry of providers) {
18538
+ const p = entry;
18539
+ if (!p?.id || !p.models || typeof p.models !== "object")
18540
+ continue;
18541
+ for (const [modelId, model] of Object.entries(p.models)) {
18542
+ const context = model?.limit?.context;
18543
+ if (typeof context === "number" && context > 0) {
18544
+ map2.set(`${p.id}/${modelId}`, context);
18545
+ }
18546
+ }
18547
+ }
18548
+ apiCache = map2;
18549
+ apiLoadedAt = Date.now();
18550
+ sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model limits`);
18551
+ } catch (error48) {
18552
+ sessionLog("global", "models-dev-cache: API refresh failed:", error48 instanceof Error ? error48.message : String(error48));
18553
+ }
18554
+ }
18555
+ function getModelsDevContextLimit(providerID, modelID) {
18556
+ const key = `${providerID}/${modelID}`;
18557
+ if (apiCache) {
18558
+ const fromApi = apiCache.get(key);
18559
+ if (typeof fromApi === "number")
18560
+ return fromApi;
18561
+ }
18562
+ const now = Date.now();
18563
+ if (!fileCache || now - fileLastAttempt > RELOAD_INTERVAL_MS) {
18564
+ fileLastAttempt = now;
18565
+ fileCache = loadModelsDevLimitsFromFile();
18566
+ }
18567
+ return fileCache.get(key);
18568
+ }
18569
+ var RELOAD_INTERVAL_MS, apiCache = null, apiLoadedAt = 0, fileCache = null, fileLastAttempt = 0;
18570
+ var init_models_dev_cache = __esm(() => {
18571
+ init_logger();
18572
+ RELOAD_INTERVAL_MS = 5 * 60 * 1000;
18573
+ });
18574
+
18377
18575
  // src/features/magic-context/memory/project-identity.ts
18378
18576
  import { execSync } from "child_process";
18379
18577
  import path3 from "path";
@@ -18566,6 +18764,67 @@ var init_send_session_notification = __esm(() => {
18566
18764
  init_logger();
18567
18765
  });
18568
18766
 
18767
+ // src/hooks/magic-context/derive-budgets.ts
18768
+ var exports_derive_budgets = {};
18769
+ __export(exports_derive_budgets, {
18770
+ resolveHistorianContextLimit: () => resolveHistorianContextLimit,
18771
+ deriveTriggerBudget: () => deriveTriggerBudget,
18772
+ deriveHistorianChunkTokens: () => deriveHistorianChunkTokens
18773
+ });
18774
+ function deriveTriggerBudget(mainContextLimit, executeThresholdPercentage) {
18775
+ if (!Number.isFinite(mainContextLimit) || mainContextLimit <= 0) {
18776
+ return TRIGGER_BUDGET_MIN;
18777
+ }
18778
+ const thresholdFraction = Math.max(0, executeThresholdPercentage) / 100;
18779
+ const usable = mainContextLimit * thresholdFraction;
18780
+ const derived = Math.round(usable * TRIGGER_BUDGET_PERCENTAGE);
18781
+ return Math.max(TRIGGER_BUDGET_MIN, Math.min(TRIGGER_BUDGET_MAX, derived));
18782
+ }
18783
+ function deriveHistorianChunkTokens(historianContextLimit) {
18784
+ if (!Number.isFinite(historianContextLimit) || historianContextLimit <= 0) {
18785
+ return HISTORIAN_CHUNK_MIN;
18786
+ }
18787
+ const derived = Math.round(historianContextLimit * HISTORIAN_CHUNK_PERCENTAGE);
18788
+ return Math.max(HISTORIAN_CHUNK_MIN, Math.min(HISTORIAN_CHUNK_MAX, derived));
18789
+ }
18790
+ function resolveHistorianContextLimit(historianModelOverride) {
18791
+ if (typeof historianModelOverride === "string" && historianModelOverride.includes("/")) {
18792
+ const [providerID, ...rest] = historianModelOverride.split("/");
18793
+ const modelID = rest.join("/");
18794
+ if (providerID && modelID) {
18795
+ const limit = getModelsDevContextLimit(providerID, modelID);
18796
+ if (typeof limit === "number" && limit > 0)
18797
+ return limit;
18798
+ }
18799
+ return DEFAULT_HISTORIAN_CONTEXT_FALLBACK;
18800
+ }
18801
+ if (typeof historianModelOverride === "string" && historianModelOverride.trim() !== "") {
18802
+ console.warn(`[magic-context] historian.model "${historianModelOverride}" lacks provider prefix ("provider/model-id"); using fallback chain for chunk-budget derivation.`);
18803
+ }
18804
+ const chain = AGENT_MODEL_REQUIREMENTS[HISTORIAN_AGENT]?.fallbackChain;
18805
+ if (!chain || chain.length === 0)
18806
+ return DEFAULT_HISTORIAN_CONTEXT_FALLBACK;
18807
+ const expanded = expandFallbackChain(chain);
18808
+ let minLimit;
18809
+ for (const key of expanded) {
18810
+ const [providerID, ...rest] = key.split("/");
18811
+ const modelID = rest.join("/");
18812
+ if (!providerID || !modelID)
18813
+ continue;
18814
+ const limit = getModelsDevContextLimit(providerID, modelID);
18815
+ if (typeof limit !== "number" || limit <= 0)
18816
+ continue;
18817
+ if (minLimit === undefined || limit < minLimit)
18818
+ minLimit = limit;
18819
+ }
18820
+ return minLimit ?? DEFAULT_HISTORIAN_CONTEXT_FALLBACK;
18821
+ }
18822
+ var TRIGGER_BUDGET_PERCENTAGE = 0.05, TRIGGER_BUDGET_MIN = 5000, TRIGGER_BUDGET_MAX = 50000, HISTORIAN_CHUNK_PERCENTAGE = 0.25, HISTORIAN_CHUNK_MIN = 8000, HISTORIAN_CHUNK_MAX = 50000, DEFAULT_HISTORIAN_CONTEXT_FALLBACK = 128000;
18823
+ var init_derive_budgets = __esm(() => {
18824
+ init_model_requirements();
18825
+ init_models_dev_cache();
18826
+ });
18827
+
18569
18828
  // src/features/magic-context/compaction-marker.ts
18570
18829
  import { Database as Database4 } from "bun:sqlite";
18571
18830
  import { join as join12 } from "path";
@@ -19980,7 +20239,7 @@ async function runCompartmentAgent(deps) {
19980
20239
  client,
19981
20240
  db,
19982
20241
  sessionId,
19983
- tokenBudget,
20242
+ historianChunkTokens,
19984
20243
  directory,
19985
20244
  historianTimeoutMs,
19986
20245
  getNotificationParams
@@ -20015,7 +20274,7 @@ No new compartments or facts were written. Rebuild or clear the broken compartme
20015
20274
  if (protectedTailStart <= offset) {
20016
20275
  return;
20017
20276
  }
20018
- const chunk = readSessionChunk(sessionId, tokenBudget, offset, protectedTailStart);
20277
+ const chunk = readSessionChunk(sessionId, historianChunkTokens, offset, protectedTailStart);
20019
20278
  if (!chunk.text || chunk.messageCount === 0) {
20020
20279
  return;
20021
20280
  }
@@ -20158,7 +20417,7 @@ async function executeContextRecompInternal(deps) {
20158
20417
  client,
20159
20418
  db,
20160
20419
  sessionId,
20161
- tokenBudget,
20420
+ historianChunkTokens,
20162
20421
  directory,
20163
20422
  historianTimeoutMs,
20164
20423
  getNotificationParams
@@ -20212,7 +20471,7 @@ No eligible raw history exists before the protected tail, so nothing was rebuilt
20212
20471
  let candidateFacts = existingStaging?.facts ?? [];
20213
20472
  let offset = existingStaging ? existingStaging.lastEndMessage + 1 : 1;
20214
20473
  let passCount = existingStaging?.passCount ?? 0;
20215
- let currentTokenBudget = tokenBudget;
20474
+ let currentTokenBudget = historianChunkTokens;
20216
20475
  let passAttempt = 1;
20217
20476
  const resumed = existingStaging !== null;
20218
20477
  if (resumed) {
@@ -20310,7 +20569,7 @@ Nothing was written.`;
20310
20569
  ];
20311
20570
  candidateFacts = validatedPass.facts ?? [];
20312
20571
  passCount += 1;
20313
- currentTokenBudget = tokenBudget;
20572
+ currentTokenBudget = historianChunkTokens;
20314
20573
  passAttempt = 1;
20315
20574
  saveRecompStagingPass(db, sessionId, passCount, candidateCompartments, candidateFacts);
20316
20575
  const nextOffset = (validatedPass.compartments?.[validatedPass.compartments.length - 1]?.endMessage ?? chunk.endIndex) + 1;
@@ -28510,7 +28769,7 @@ Check verifiable memories against actual repository state. Update stale wording,
28510
28769
  4. **Be conservative.** If you cannot find the referenced code but it might be in a location you haven't checked, do NOT archive. Move on.
28511
28770
 
28512
28771
  ### Verification examples
28513
- - Memory: "compartment_token_budget defaults to 20000" \u2192 grep schema for \`compartment_token_budget\`, check \`.default(...)\`
28772
+ - Memory: "history_budget_percentage defaults to 0.15" \u2192 grep schema for \`history_budget_percentage\`, check \`.default(...)\`
28514
28773
  - Memory: "Durable state lives in ~/.local/share/opencode/storage/plugin/magic-context/context.db" \u2192 check storage-db.ts for the path construction
28515
28774
  - Memory: "ctx_search searches memories, facts, and history" \u2192 grep for ctx_search tool definition and unified search implementation
28516
28775
 
@@ -30239,110 +30498,13 @@ function createCompactionHandler() {
30239
30498
  }
30240
30499
  };
30241
30500
  }
30242
- // src/shared/models-dev-cache.ts
30243
- init_logger();
30244
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
30245
- import { homedir as homedir5, platform as platform2 } from "os";
30246
- import { join as join11 } from "path";
30247
- var cachedLimits = null;
30248
- var lastLoadAttempt = 0;
30249
- var RELOAD_INTERVAL_MS = 5 * 60 * 1000;
30250
- function getModelsJsonPath() {
30251
- const xdgCache = process.env.XDG_CACHE_HOME;
30252
- const os3 = platform2();
30253
- let cacheBase;
30254
- if (xdgCache) {
30255
- cacheBase = xdgCache;
30256
- } else if (os3 === "win32") {
30257
- cacheBase = process.env.LOCALAPPDATA ?? join11(homedir5(), "AppData", "Local");
30258
- } else {
30259
- cacheBase = join11(homedir5(), ".cache");
30260
- }
30261
- return join11(cacheBase, "opencode", "models.json");
30262
- }
30263
- function getOpencodeConfigPath() {
30264
- const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
30265
- const configDir = envDir ? envDir : platform2() === "win32" ? join11(homedir5(), ".config", "opencode") : join11(process.env.XDG_CONFIG_HOME || join11(homedir5(), ".config"), "opencode");
30266
- const jsonc = join11(configDir, "opencode.jsonc");
30267
- if (existsSync5(jsonc))
30268
- return jsonc;
30269
- const json2 = join11(configDir, "opencode.json");
30270
- if (existsSync5(json2))
30271
- return json2;
30272
- return null;
30273
- }
30274
- function loadModelsDevLimits() {
30275
- const limits = new Map;
30276
- const modelsJsonPath = getModelsJsonPath();
30277
- try {
30278
- if (existsSync5(modelsJsonPath)) {
30279
- const raw = readFileSync4(modelsJsonPath, "utf-8");
30280
- const data = JSON.parse(raw);
30281
- for (const [providerId, provider2] of Object.entries(data)) {
30282
- if (!provider2?.models || typeof provider2.models !== "object")
30283
- continue;
30284
- for (const [modelId, model] of Object.entries(provider2.models)) {
30285
- const context = model?.limit?.context;
30286
- if (typeof context === "number" && context > 0) {
30287
- limits.set(`${providerId}/${modelId}`, context);
30288
- const modes = model?.experimental?.modes;
30289
- if (modes && typeof modes === "object") {
30290
- for (const mode of Object.keys(modes)) {
30291
- limits.set(`${providerId}/${modelId}-${mode}`, context);
30292
- }
30293
- }
30294
- }
30295
- }
30296
- }
30297
- }
30298
- } catch (error48) {
30299
- sessionLog("global", "models-dev-cache: failed to read models.json:", error48 instanceof Error ? error48.message : String(error48));
30300
- }
30301
- try {
30302
- const configPath = getOpencodeConfigPath();
30303
- if (configPath && existsSync5(configPath)) {
30304
- let raw = readFileSync4(configPath, "utf-8");
30305
- raw = raw.replace(/"(?:[^"\\]|\\.)*"|\/\/.*$/gm, (match) => match.startsWith('"') ? match : "");
30306
- const config2 = JSON.parse(raw);
30307
- if (config2.provider && typeof config2.provider === "object") {
30308
- for (const [providerId, provider2] of Object.entries(config2.provider)) {
30309
- if (!provider2?.models || typeof provider2.models !== "object")
30310
- continue;
30311
- for (const [modelId, model] of Object.entries(provider2.models)) {
30312
- const context = model?.limit?.context;
30313
- if (typeof context === "number" && context > 0) {
30314
- limits.set(`${providerId}/${modelId}`, context);
30315
- }
30316
- }
30317
- }
30318
- }
30319
- }
30320
- } catch (error48) {
30321
- sessionLog("global", "models-dev-cache: failed to read opencode config for custom models:", error48 instanceof Error ? error48.message : String(error48));
30322
- }
30323
- return limits;
30324
- }
30325
- function getModelsDevContextLimit(providerID, modelID) {
30326
- const now = Date.now();
30327
- if (!cachedLimits || now - lastLoadAttempt > RELOAD_INTERVAL_MS) {
30328
- lastLoadAttempt = now;
30329
- cachedLimits = loadModelsDevLimits();
30330
- }
30331
- return cachedLimits.get(`${providerID}/${modelID}`);
30332
- }
30333
-
30334
30501
  // src/hooks/magic-context/event-resolvers.ts
30502
+ init_models_dev_cache();
30335
30503
  var DEFAULT_CONTEXT_LIMIT = 128000;
30336
- function resolveContextLimit(providerID, modelID, config2) {
30504
+ function resolveContextLimit(providerID, modelID) {
30337
30505
  if (!providerID) {
30338
30506
  return DEFAULT_CONTEXT_LIMIT;
30339
30507
  }
30340
- if (modelID) {
30341
- const modelSpecific = config2.modelContextLimitsCache?.get(`${providerID}/${modelID}`);
30342
- if (typeof modelSpecific === "number" && modelSpecific > 0) {
30343
- return modelSpecific;
30344
- }
30345
- }
30346
30508
  if (modelID) {
30347
30509
  const modelsDevLimit = getModelsDevContextLimit(providerID, modelID);
30348
30510
  if (modelsDevLimit !== undefined) {
@@ -30625,7 +30787,6 @@ init_storage_tags();
30625
30787
  init_logger();
30626
30788
 
30627
30789
  // src/hooks/magic-context/compartment-trigger.ts
30628
- init_magic_context();
30629
30790
  init_compartment_storage();
30630
30791
  init_storage();
30631
30792
  init_logger();
@@ -30703,7 +30864,7 @@ var TAIL_INFO_DEFAULTS = {
30703
30864
  tokenEstimate: 0,
30704
30865
  commitClusterCount: 0
30705
30866
  };
30706
- function getUnsummarizedTailInfo(db, sessionId, compartmentTokenBudget) {
30867
+ function getUnsummarizedTailInfo(db, sessionId, triggerBudget) {
30707
30868
  return withRawSessionMessageCache(() => {
30708
30869
  try {
30709
30870
  const lastCompartmentEnd = getLastCompartmentEndMessage(db, sessionId);
@@ -30714,7 +30875,7 @@ function getUnsummarizedTailInfo(db, sessionId, compartmentTokenBudget) {
30714
30875
  if (!hasEligibleHistory) {
30715
30876
  return { ...TAIL_INFO_DEFAULTS, nextStartOrdinal };
30716
30877
  }
30717
- const scanBudget = Math.max(MIN_PROACTIVE_TAIL_TOKEN_ESTIMATE, compartmentTokenBudget * TAIL_SIZE_TRIGGER_MULTIPLIER);
30878
+ const scanBudget = Math.max(MIN_PROACTIVE_TAIL_TOKEN_ESTIMATE, triggerBudget * TAIL_SIZE_TRIGGER_MULTIPLIER);
30718
30879
  const chunk = readSessionChunk(sessionId, scanBudget, nextStartOrdinal, protectedTailStart);
30719
30880
  const isMeaningful = chunk.hasMore || chunk.tokenEstimate >= MIN_PROACTIVE_TAIL_TOKEN_ESTIMATE || chunk.messageCount >= MIN_PROACTIVE_TAIL_MESSAGE_COUNT;
30720
30881
  return {
@@ -30730,11 +30891,11 @@ function getUnsummarizedTailInfo(db, sessionId, compartmentTokenBudget) {
30730
30891
  }
30731
30892
  });
30732
30893
  }
30733
- function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPercentage, executeThresholdPercentage, compartmentTokenBudget = DEFAULT_COMPARTMENT_TOKEN_BUDGET, autoDropToolAge, protectedTagCount, clearReasoningAge, dropToolStructure = true, commitClusterTrigger) {
30894
+ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPercentage, executeThresholdPercentage, triggerBudget, autoDropToolAge, protectedTagCount, clearReasoningAge, dropToolStructure = true, commitClusterTrigger) {
30734
30895
  if (sessionMeta.compartmentInProgress) {
30735
30896
  return { shouldFire: false };
30736
30897
  }
30737
- const tailInfo = getUnsummarizedTailInfo(db, sessionId, compartmentTokenBudget);
30898
+ const tailInfo = getUnsummarizedTailInfo(db, sessionId, triggerBudget);
30738
30899
  if (!tailInfo.hasNewRawHistory) {
30739
30900
  return { shouldFire: false };
30740
30901
  }
@@ -30750,12 +30911,12 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
30750
30911
  }
30751
30912
  const clusterEnabled = commitClusterTrigger?.enabled ?? true;
30752
30913
  const minClusters = commitClusterTrigger?.min_clusters ?? DEFAULT_MIN_COMMIT_CLUSTERS_FOR_TRIGGER;
30753
- if (clusterEnabled && tailInfo.commitClusterCount >= minClusters && tailInfo.tokenEstimate >= compartmentTokenBudget) {
30914
+ if (clusterEnabled && tailInfo.commitClusterCount >= minClusters && tailInfo.tokenEstimate >= triggerBudget) {
30754
30915
  sessionLog(sessionId, `compartment trigger: commit-cluster fire \u2014 ${tailInfo.commitClusterCount} clusters (min=${minClusters}), ~${tailInfo.tokenEstimate} tokens in eligible prefix`);
30755
30916
  return { shouldFire: true, reason: "commit_clusters" };
30756
30917
  }
30757
- if (tailInfo.tokenEstimate >= compartmentTokenBudget * TAIL_SIZE_TRIGGER_MULTIPLIER) {
30758
- sessionLog(sessionId, `compartment trigger: tail-size fire \u2014 ~${tailInfo.tokenEstimate} tokens exceeds ${compartmentTokenBudget * TAIL_SIZE_TRIGGER_MULTIPLIER} budget threshold`);
30918
+ if (tailInfo.tokenEstimate >= triggerBudget * TAIL_SIZE_TRIGGER_MULTIPLIER) {
30919
+ sessionLog(sessionId, `compartment trigger: tail-size fire \u2014 ~${tailInfo.tokenEstimate} tokens exceeds ${triggerBudget * TAIL_SIZE_TRIGGER_MULTIPLIER} budget threshold`);
30759
30920
  return { shouldFire: true, reason: "tail_size" };
30760
30921
  }
30761
30922
  const proactiveTriggerPercentage = getProactiveCompartmentTriggerPercentage(executeThresholdPercentage);
@@ -31099,11 +31260,15 @@ Historian recomp started. Rebuilding compartments and facts from raw session his
31099
31260
  };
31100
31261
  }
31101
31262
 
31263
+ // src/hooks/magic-context/hook.ts
31264
+ init_derive_budgets();
31265
+
31102
31266
  // src/hooks/magic-context/event-handler.ts
31103
31267
  init_storage();
31104
31268
  init_storage_meta_persisted();
31105
31269
  init_logger();
31106
31270
  init_compaction_marker_manager();
31271
+ init_derive_budgets();
31107
31272
 
31108
31273
  // src/hooks/magic-context/event-payloads.ts
31109
31274
  function getSessionProperties(properties) {
@@ -31175,7 +31340,6 @@ function getMessageRemovedInfo(properties) {
31175
31340
  init_note_nudger();
31176
31341
 
31177
31342
  // src/hooks/magic-context/transform-compartment-phase.ts
31178
- init_magic_context();
31179
31343
  init_compartment_storage();
31180
31344
  init_storage();
31181
31345
  init_logger();
@@ -31251,7 +31415,7 @@ async function runCompartmentPhase(args) {
31251
31415
  client: args.client,
31252
31416
  db: args.db,
31253
31417
  sessionId: args.sessionId,
31254
- tokenBudget: args.compartmentTokenBudget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET,
31418
+ historianChunkTokens: args.historianChunkTokens,
31255
31419
  historyBudgetTokens: args.historyBudgetTokens,
31256
31420
  historianTimeoutMs: args.historianTimeoutMs,
31257
31421
  directory: args.compartmentDirectory,
@@ -31272,7 +31436,7 @@ async function runCompartmentPhase(args) {
31272
31436
  client: args.client,
31273
31437
  db: args.db,
31274
31438
  sessionId: args.sessionId,
31275
- tokenBudget: args.compartmentTokenBudget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET,
31439
+ historianChunkTokens: args.historianChunkTokens,
31276
31440
  historyBudgetTokens: args.historyBudgetTokens,
31277
31441
  historianTimeoutMs: args.historianTimeoutMs,
31278
31442
  directory: args.compartmentDirectory,
@@ -31425,9 +31589,7 @@ function createEventHandler2(deps) {
31425
31589
  }
31426
31590
  if (hasUsageTokens) {
31427
31591
  const totalInputTokens = (info.tokens?.input ?? 0) + (info.tokens?.cache?.read ?? 0) + (info.tokens?.cache?.write ?? 0);
31428
- const contextLimit = resolveContextLimit(info.providerID, info.modelID, {
31429
- modelContextLimitsCache: deps.config.modelContextLimitsCache
31430
- });
31592
+ const contextLimit = resolveContextLimit(info.providerID, info.modelID);
31431
31593
  const percentage = contextLimit > 0 ? totalInputTokens / contextLimit * 100 : 0;
31432
31594
  sessionLog(info.sessionID, `event message.updated: totalInputTokens=${totalInputTokens} contextLimit=${contextLimit} percentage=${percentage.toFixed(1)}%`);
31433
31595
  deps.contextUsageMap.set(info.sessionID, {
@@ -31447,7 +31609,9 @@ function createEventHandler2(deps) {
31447
31609
  const sessionMeta = getOrCreateSessionMeta(deps.db, info.sessionID);
31448
31610
  const previousPercentage = sessionMeta.lastContextPercentage;
31449
31611
  if (!sessionMeta.isSubagent) {
31450
- const triggerResult = checkCompartmentTrigger(deps.db, info.sessionID, sessionMeta, { percentage, inputTokens: totalInputTokens }, previousPercentage, resolveExecuteThreshold(deps.config.execute_threshold_percentage ?? 65, modelKey, 65), undefined, deps.config.auto_drop_tool_age ?? 100, deps.config.protected_tags, deps.config.clear_reasoning_age ?? 50, deps.config.drop_tool_structure ?? true, deps.config.commit_cluster_trigger);
31612
+ const effectiveExecuteThreshold = resolveExecuteThreshold(deps.config.execute_threshold_percentage ?? 65, modelKey, 65);
31613
+ const triggerBudget = deriveTriggerBudget(contextLimit, effectiveExecuteThreshold);
31614
+ const triggerResult = checkCompartmentTrigger(deps.db, info.sessionID, sessionMeta, { percentage, inputTokens: totalInputTokens }, previousPercentage, effectiveExecuteThreshold, triggerBudget, deps.config.auto_drop_tool_age ?? 100, deps.config.protected_tags, deps.config.clear_reasoning_age ?? 50, deps.config.drop_tool_structure ?? true, deps.config.commit_cluster_trigger);
31451
31615
  if (triggerResult.shouldFire) {
31452
31616
  sessionLog(info.sessionID, `compartment trigger: firing (reason=${triggerResult.reason})`);
31453
31617
  updateSessionMeta(deps.db, info.sessionID, {
@@ -31725,7 +31889,6 @@ function createTextCompleteHandler() {
31725
31889
  }
31726
31890
 
31727
31891
  // src/hooks/magic-context/transform.ts
31728
- init_magic_context();
31729
31892
  init_compartment_storage();
31730
31893
  init_project_identity();
31731
31894
  init_storage();
@@ -32806,6 +32969,14 @@ function isToolProtocolPart(part) {
32806
32969
  function hasToolProtocolParts(message) {
32807
32970
  return message.parts.some(isToolProtocolPart);
32808
32971
  }
32972
+ function hasThinkingBearingParts(message) {
32973
+ return message.parts.some((part) => {
32974
+ if (part === null || typeof part !== "object")
32975
+ return false;
32976
+ const p = part;
32977
+ return p.type === "thinking" || p.type === "reasoning" || p.type === "redacted_thinking";
32978
+ });
32979
+ }
32809
32980
  function isMessageDropped(message) {
32810
32981
  const textParts = message.parts.filter(isTextPart);
32811
32982
  if (textParts.length === 0)
@@ -32840,6 +33011,11 @@ function reinjectNudgeAtAnchor(messages, nudgeText, nudgePlacements, sessionId)
32840
33011
  nudgePlacements.clear(sessionId);
32841
33012
  return false;
32842
33013
  }
33014
+ if (hasThinkingBearingParts(message)) {
33015
+ sessionLog(sessionId, `nudge anchor abandoned: message ${message.info.id} now contains thinking/reasoning parts (signed, immutable)`);
33016
+ nudgePlacements.clear(sessionId);
33017
+ return false;
33018
+ }
32843
33019
  for (let j = message.parts.length - 1;j >= 0; j--) {
32844
33020
  const part = message.parts[j];
32845
33021
  if (isTextPart(part)) {
@@ -32868,6 +33044,8 @@ function appendNudgeToAssistant(messages, nudge, nudgePlacements, sessionId) {
32868
33044
  continue;
32869
33045
  if (isMessageDropped(message))
32870
33046
  continue;
33047
+ if (hasThinkingBearingParts(message))
33048
+ continue;
32871
33049
  for (let j = message.parts.length - 1;j >= 0; j--) {
32872
33050
  const part = message.parts[j];
32873
33051
  if (isTextPart(part)) {
@@ -33151,12 +33329,13 @@ function runPostTransformPhase(args) {
33151
33329
  const forceMaterialization = args.fullFeatureMode && args.contextUsage.percentage >= args.forceMaterializationPercentage;
33152
33330
  const activeCompartmentRun = args.canRunCompartments ? getActiveCompartmentRun(args.sessionId) : undefined;
33153
33331
  const compartmentRunning = args.canRunCompartments && !args.awaitedCompartmentRun && activeCompartmentRun !== undefined;
33154
- const shouldReadPendingOps = isExplicitFlush || args.schedulerDecision === "execute" || compartmentRunning;
33332
+ const emergencyBypassCompartmentGate = forceMaterialization;
33333
+ const shouldReadPendingOps = isExplicitFlush || args.schedulerDecision === "execute" || forceMaterialization || compartmentRunning;
33155
33334
  const pendingOps = shouldReadPendingOps ? getPendingOps(args.db, args.sessionId) : [];
33156
33335
  const hasPendingUserOps = pendingOps.length > 0;
33157
- const shouldApplyPendingOps = (args.schedulerDecision === "execute" || isExplicitFlush) && !compartmentRunning;
33336
+ const shouldApplyPendingOps = (args.schedulerDecision === "execute" || isExplicitFlush || forceMaterialization) && (!compartmentRunning || emergencyBypassCompartmentGate);
33158
33337
  const isCacheBustingPass = isExplicitFlush || shouldApplyPendingOps;
33159
- const shouldRunHeuristics = args.fullFeatureMode && !compartmentRunning && (isExplicitFlush || forceMaterialization || args.schedulerDecision === "execute" && !alreadyRanThisTurn);
33338
+ const shouldRunHeuristics = args.fullFeatureMode && (!compartmentRunning || emergencyBypassCompartmentGate) && (isExplicitFlush || forceMaterialization || args.schedulerDecision === "execute" && !alreadyRanThisTurn);
33160
33339
  if (shouldRunHeuristics) {
33161
33340
  const reason = isExplicitFlush ? "explicit_flush" : forceMaterialization ? `force_materialization (${args.contextUsage.percentage.toFixed(1)}% >= ${args.forceMaterializationPercentage}%)` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
33162
33341
  sessionLog(args.sessionId, `heuristics WILL RUN \u2014 reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=${args.currentTurnId}`);
@@ -33165,7 +33344,11 @@ function runPostTransformPhase(args) {
33165
33344
  sessionLog(args.sessionId, `transform: skipping heuristics (already ran for turn ${args.currentTurnId})`);
33166
33345
  }
33167
33346
  if (compartmentRunning && hasPendingUserOps) {
33168
- sessionLog(args.sessionId, "transform: deferring pending ops \u2014 compartment agent in progress");
33347
+ if (emergencyBypassCompartmentGate) {
33348
+ sessionLog(args.sessionId, `transform: emergency bypass \u2014 applying ${pendingOps.length} pending ops while compartment agent runs (${args.contextUsage.percentage.toFixed(1)}%)`);
33349
+ } else {
33350
+ sessionLog(args.sessionId, "transform: deferring pending ops \u2014 compartment agent in progress");
33351
+ }
33169
33352
  }
33170
33353
  try {
33171
33354
  if (shouldApplyPendingOps) {
@@ -33524,7 +33707,7 @@ function createTransform(deps) {
33524
33707
  client: deps.client,
33525
33708
  db,
33526
33709
  sessionId,
33527
- tokenBudget: deps.compartmentTokenBudget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET,
33710
+ historianChunkTokens: deps.getHistorianChunkTokens?.() ?? 20000,
33528
33711
  historyBudgetTokens,
33529
33712
  historianTimeoutMs: deps.historianTimeoutMs,
33530
33713
  directory: compartmentDirectory,
@@ -33645,7 +33828,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
33645
33828
  db,
33646
33829
  sessionId,
33647
33830
  resolvedSessionId,
33648
- compartmentTokenBudget: deps.compartmentTokenBudget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET,
33831
+ historianChunkTokens: deps.getHistorianChunkTokens?.() ?? 20000,
33649
33832
  historyBudgetTokens,
33650
33833
  historianTimeoutMs: deps.historianTimeoutMs,
33651
33834
  compartmentDirectory,
@@ -34352,6 +34535,7 @@ function createMagicContextHook(deps) {
34352
34535
  const projectPath = resolveProjectIdentity(deps.directory);
34353
34536
  registerDreamProjectDirectory(projectPath, deps.directory);
34354
34537
  let lastScheduleCheckMs = 0;
34538
+ const getHistorianChunkTokens = () => deriveHistorianChunkTokens(resolveHistorianContextLimit(deps.config.historian?.model));
34355
34539
  const nudgePlacements = createNudgePlacementStore(db);
34356
34540
  const flushedSessions = new Set;
34357
34541
  const lastHeuristicsTurnId = new Map;
@@ -34389,7 +34573,7 @@ function createMagicContextHook(deps) {
34389
34573
  enabled: deps.config.memory.enabled,
34390
34574
  injectionBudgetTokens: deps.config.memory.injection_budget_tokens
34391
34575
  } : undefined,
34392
- compartmentTokenBudget: deps.config.compartment_token_budget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET,
34576
+ getHistorianChunkTokens,
34393
34577
  historyBudgetPercentage: deps.config.history_budget_percentage,
34394
34578
  executeThresholdPercentage: deps.config.execute_threshold_percentage,
34395
34579
  historianTimeoutMs: deps.config.historian_timeout_ms ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
@@ -34470,7 +34654,7 @@ function createMagicContextHook(deps) {
34470
34654
  client: deps.client,
34471
34655
  db,
34472
34656
  sessionId,
34473
- tokenBudget: deps.config.compartment_token_budget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET,
34657
+ historianChunkTokens: getHistorianChunkTokens(),
34474
34658
  historianTimeoutMs: deps.config.historian_timeout_ms ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
34475
34659
  directory: deps.directory,
34476
34660
  fallbackModelId: (() => {
@@ -34596,7 +34780,7 @@ function createSessionHooks(args) {
34596
34780
  clear_reasoning_age: pluginConfig.clear_reasoning_age,
34597
34781
  iteration_nudge_threshold: pluginConfig.iteration_nudge_threshold,
34598
34782
  execute_threshold_percentage: pluginConfig.execute_threshold_percentage ?? DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE,
34599
- compartment_token_budget: pluginConfig.compartment_token_budget,
34783
+ historian: pluginConfig.historian,
34600
34784
  history_budget_percentage: pluginConfig.history_budget_percentage,
34601
34785
  historian_timeout_ms: pluginConfig.historian_timeout_ms,
34602
34786
  memory: pluginConfig.memory,
@@ -34921,17 +35105,18 @@ function registerRpcHandlers(rpcServer, args) {
34921
35105
  return { ok: false, error: "no session" };
34922
35106
  const { executeContextRecomp: executeContextRecomp2 } = await Promise.resolve().then(() => (init_compartment_runner(), exports_compartment_runner));
34923
35107
  const { sendIgnoredMessage: sendIgnoredMessage2 } = await Promise.resolve().then(() => (init_send_session_notification(), exports_send_session_notification));
35108
+ const { deriveHistorianChunkTokens: deriveHistorianChunkTokens2, resolveHistorianContextLimit: resolveHistorianContextLimit2 } = await Promise.resolve().then(() => (init_derive_budgets(), exports_derive_budgets));
34924
35109
  const db = getDb();
34925
35110
  if (!db)
34926
35111
  return { ok: false, error: "db unavailable" };
34927
- const DEFAULT_COMPARTMENT_TOKEN_BUDGET2 = 20000;
34928
35112
  const DEFAULT_HISTORIAN_TIMEOUT_MS2 = 600000;
35113
+ const historianChunkTokens = deriveHistorianChunkTokens2(resolveHistorianContextLimit2(config2.historian?.model));
34929
35114
  log(`[rpc] recomp requested for session ${sessionId}`);
34930
35115
  executeContextRecomp2({
34931
35116
  client: args.client,
34932
35117
  db,
34933
35118
  sessionId,
34934
- tokenBudget: config2.compartment_token_budget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET2,
35119
+ historianChunkTokens,
34935
35120
  historianTimeoutMs: config2.historian_timeout_ms ?? DEFAULT_HISTORIAN_TIMEOUT_MS2,
34936
35121
  directory,
34937
35122
  getNotificationParams: () => getNotificationParams(sessionId)
@@ -36136,6 +36321,7 @@ init_conflict_detector();
36136
36321
  init_data_path();
36137
36322
  init_logger();
36138
36323
  init_model_requirements();
36324
+ init_models_dev_cache();
36139
36325
 
36140
36326
  // src/shared/rpc-server.ts
36141
36327
  init_logger();
@@ -36144,11 +36330,11 @@ import { createServer } from "http";
36144
36330
  import { dirname } from "path";
36145
36331
 
36146
36332
  // src/shared/rpc-utils.ts
36147
- import { createHash } from "crypto";
36333
+ import { createHash as createHash2 } from "crypto";
36148
36334
  import { join as join15 } from "path";
36149
36335
  function projectHash(directory) {
36150
36336
  const normalized = directory.replace(/\/+$/, "");
36151
- return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
36337
+ return createHash2("sha256").update(normalized).digest("hex").slice(0, 16);
36152
36338
  }
36153
36339
  function rpcPortFilePath(storageDir, directory) {
36154
36340
  return join15(storageDir, "rpc", projectHash(directory), "port");
@@ -36333,6 +36519,10 @@ var plugin = async (ctx) => {
36333
36519
  rpcServer.start().catch((err) => {
36334
36520
  log(`[magic-context] RPC server failed to start: ${err}`);
36335
36521
  });
36522
+ refreshModelLimitsFromApi(ctx.client);
36523
+ setInterval(() => {
36524
+ refreshModelLimitsFromApi(ctx.client);
36525
+ }, 5 * 60 * 1000);
36336
36526
  }
36337
36527
  if (conflictResult?.hasConflict) {
36338
36528
  sendConflictWarning(ctx.client, ctx.directory, conflictResult);