@cortexkit/opencode-magic-context 0.9.1 → 0.10.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 (32) 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/hooks/magic-context/compartment-runner-types.d.ts +6 -1
  6. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  7. package/dist/hooks/magic-context/compartment-trigger.d.ts +1 -1
  8. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  9. package/dist/hooks/magic-context/derive-budgets.d.ts +57 -0
  10. package/dist/hooks/magic-context/derive-budgets.d.ts.map +1 -0
  11. package/dist/hooks/magic-context/event-handler.d.ts +0 -1
  12. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  13. package/dist/hooks/magic-context/event-resolvers.d.ts +1 -3
  14. package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
  15. package/dist/hooks/magic-context/hook.d.ts +3 -2
  16. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  17. package/dist/hooks/magic-context/nudge-injection.d.ts.map +1 -1
  18. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +1 -1
  19. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  20. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  21. package/dist/hooks/magic-context/transform.d.ts +7 -1
  22. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +266 -136
  25. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  26. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  27. package/dist/plugin/tui-action-consumer.d.ts.map +1 -1
  28. package/dist/shared/models-dev-cache.d.ts +52 -4
  29. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/shared/models-dev-cache.test.ts +228 -0
  32. 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({
@@ -18374,6 +18373,145 @@ var init_storage = __esm(() => {
18374
18373
  init_storage_tags();
18375
18374
  });
18376
18375
 
18376
+ // src/shared/models-dev-cache.ts
18377
+ import { createHash } from "crypto";
18378
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
18379
+ import { homedir as homedir5, platform as platform2 } from "os";
18380
+ import { join as join11 } from "path";
18381
+ function hashFast(input) {
18382
+ return createHash("sha1").update(input).digest("hex");
18383
+ }
18384
+ function getModelsJsonPath() {
18385
+ const explicit = process.env.OPENCODE_MODELS_PATH?.trim();
18386
+ if (explicit)
18387
+ return explicit;
18388
+ const xdgCache = process.env.XDG_CACHE_HOME;
18389
+ const os3 = platform2();
18390
+ let cacheBase;
18391
+ if (xdgCache) {
18392
+ cacheBase = xdgCache;
18393
+ } else if (os3 === "win32") {
18394
+ cacheBase = process.env.LOCALAPPDATA ?? join11(homedir5(), "AppData", "Local");
18395
+ } else {
18396
+ cacheBase = join11(homedir5(), ".cache");
18397
+ }
18398
+ const source = process.env.OPENCODE_MODELS_URL?.trim();
18399
+ const filename = source && source !== "https://models.dev" ? `models-${hashFast(source)}.json` : "models.json";
18400
+ return join11(cacheBase, "opencode", filename);
18401
+ }
18402
+ function getOpencodeConfigPath() {
18403
+ const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
18404
+ const configDir = envDir ? envDir : platform2() === "win32" ? join11(homedir5(), ".config", "opencode") : join11(process.env.XDG_CONFIG_HOME || join11(homedir5(), ".config"), "opencode");
18405
+ const jsonc = join11(configDir, "opencode.jsonc");
18406
+ if (existsSync5(jsonc))
18407
+ return jsonc;
18408
+ const json2 = join11(configDir, "opencode.json");
18409
+ if (existsSync5(json2))
18410
+ return json2;
18411
+ return null;
18412
+ }
18413
+ function loadModelsDevLimitsFromFile() {
18414
+ const limits = new Map;
18415
+ const modelsJsonPath = getModelsJsonPath();
18416
+ let fileFound = false;
18417
+ try {
18418
+ if (existsSync5(modelsJsonPath)) {
18419
+ fileFound = true;
18420
+ const raw = readFileSync4(modelsJsonPath, "utf-8");
18421
+ const data = JSON.parse(raw);
18422
+ for (const [providerId, provider2] of Object.entries(data)) {
18423
+ if (!provider2?.models || typeof provider2.models !== "object")
18424
+ continue;
18425
+ for (const [modelId, model] of Object.entries(provider2.models)) {
18426
+ const context = model?.limit?.context;
18427
+ if (typeof context === "number" && context > 0) {
18428
+ limits.set(`${providerId}/${modelId}`, context);
18429
+ const modes = model?.experimental?.modes;
18430
+ if (modes && typeof modes === "object") {
18431
+ for (const mode of Object.keys(modes)) {
18432
+ limits.set(`${providerId}/${modelId}-${mode}`, context);
18433
+ }
18434
+ }
18435
+ }
18436
+ }
18437
+ }
18438
+ }
18439
+ } catch (error48) {
18440
+ sessionLog("global", `models-dev-cache: failed to read models.json at ${modelsJsonPath}:`, error48 instanceof Error ? error48.message : String(error48));
18441
+ }
18442
+ try {
18443
+ const configPath = getOpencodeConfigPath();
18444
+ if (configPath && existsSync5(configPath)) {
18445
+ let raw = readFileSync4(configPath, "utf-8");
18446
+ raw = raw.replace(/"(?:[^"\\]|\\.)*"|\/\/.*$/gm, (match) => match.startsWith('"') ? match : "");
18447
+ const config2 = JSON.parse(raw);
18448
+ if (config2.provider && typeof config2.provider === "object") {
18449
+ for (const [providerId, provider2] of Object.entries(config2.provider)) {
18450
+ if (!provider2?.models || typeof provider2.models !== "object")
18451
+ continue;
18452
+ for (const [modelId, model] of Object.entries(provider2.models)) {
18453
+ const context = model?.limit?.context;
18454
+ if (typeof context === "number" && context > 0) {
18455
+ limits.set(`${providerId}/${modelId}`, context);
18456
+ }
18457
+ }
18458
+ }
18459
+ }
18460
+ }
18461
+ } catch (error48) {
18462
+ sessionLog("global", "models-dev-cache: failed to read opencode config for custom models:", error48 instanceof Error ? error48.message : String(error48));
18463
+ }
18464
+ sessionLog("global", `models-dev-cache: file-layer loaded ${limits.size} model limits (modelsJsonPath=${modelsJsonPath}, found=${fileFound})`);
18465
+ return limits;
18466
+ }
18467
+ async function refreshModelLimitsFromApi(client) {
18468
+ try {
18469
+ const result = await client.config.providers();
18470
+ const data = result.data;
18471
+ const providers = data?.providers;
18472
+ if (!Array.isArray(providers)) {
18473
+ sessionLog("global", "models-dev-cache: API refresh returned no providers payload");
18474
+ return;
18475
+ }
18476
+ const map2 = new Map;
18477
+ for (const entry of providers) {
18478
+ const p = entry;
18479
+ if (!p?.id || !p.models || typeof p.models !== "object")
18480
+ continue;
18481
+ for (const [modelId, model] of Object.entries(p.models)) {
18482
+ const context = model?.limit?.context;
18483
+ if (typeof context === "number" && context > 0) {
18484
+ map2.set(`${p.id}/${modelId}`, context);
18485
+ }
18486
+ }
18487
+ }
18488
+ apiCache = map2;
18489
+ apiLoadedAt = Date.now();
18490
+ sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model limits`);
18491
+ } catch (error48) {
18492
+ sessionLog("global", "models-dev-cache: API refresh failed:", error48 instanceof Error ? error48.message : String(error48));
18493
+ }
18494
+ }
18495
+ function getModelsDevContextLimit(providerID, modelID) {
18496
+ const key = `${providerID}/${modelID}`;
18497
+ if (apiCache) {
18498
+ const fromApi = apiCache.get(key);
18499
+ if (typeof fromApi === "number")
18500
+ return fromApi;
18501
+ }
18502
+ const now = Date.now();
18503
+ if (!fileCache || now - fileLastAttempt > RELOAD_INTERVAL_MS) {
18504
+ fileLastAttempt = now;
18505
+ fileCache = loadModelsDevLimitsFromFile();
18506
+ }
18507
+ return fileCache.get(key);
18508
+ }
18509
+ var RELOAD_INTERVAL_MS, apiCache = null, apiLoadedAt = 0, fileCache = null, fileLastAttempt = 0;
18510
+ var init_models_dev_cache = __esm(() => {
18511
+ init_logger();
18512
+ RELOAD_INTERVAL_MS = 5 * 60 * 1000;
18513
+ });
18514
+
18377
18515
  // src/features/magic-context/memory/project-identity.ts
18378
18516
  import { execSync } from "child_process";
18379
18517
  import path3 from "path";
@@ -18566,6 +18704,67 @@ var init_send_session_notification = __esm(() => {
18566
18704
  init_logger();
18567
18705
  });
18568
18706
 
18707
+ // src/hooks/magic-context/derive-budgets.ts
18708
+ var exports_derive_budgets = {};
18709
+ __export(exports_derive_budgets, {
18710
+ resolveHistorianContextLimit: () => resolveHistorianContextLimit,
18711
+ deriveTriggerBudget: () => deriveTriggerBudget,
18712
+ deriveHistorianChunkTokens: () => deriveHistorianChunkTokens
18713
+ });
18714
+ function deriveTriggerBudget(mainContextLimit, executeThresholdPercentage) {
18715
+ if (!Number.isFinite(mainContextLimit) || mainContextLimit <= 0) {
18716
+ return TRIGGER_BUDGET_MIN;
18717
+ }
18718
+ const thresholdFraction = Math.max(0, executeThresholdPercentage) / 100;
18719
+ const usable = mainContextLimit * thresholdFraction;
18720
+ const derived = Math.round(usable * TRIGGER_BUDGET_PERCENTAGE);
18721
+ return Math.max(TRIGGER_BUDGET_MIN, Math.min(TRIGGER_BUDGET_MAX, derived));
18722
+ }
18723
+ function deriveHistorianChunkTokens(historianContextLimit) {
18724
+ if (!Number.isFinite(historianContextLimit) || historianContextLimit <= 0) {
18725
+ return HISTORIAN_CHUNK_MIN;
18726
+ }
18727
+ const derived = Math.round(historianContextLimit * HISTORIAN_CHUNK_PERCENTAGE);
18728
+ return Math.max(HISTORIAN_CHUNK_MIN, Math.min(HISTORIAN_CHUNK_MAX, derived));
18729
+ }
18730
+ function resolveHistorianContextLimit(historianModelOverride) {
18731
+ if (typeof historianModelOverride === "string" && historianModelOverride.includes("/")) {
18732
+ const [providerID, ...rest] = historianModelOverride.split("/");
18733
+ const modelID = rest.join("/");
18734
+ if (providerID && modelID) {
18735
+ const limit = getModelsDevContextLimit(providerID, modelID);
18736
+ if (typeof limit === "number" && limit > 0)
18737
+ return limit;
18738
+ }
18739
+ return DEFAULT_HISTORIAN_CONTEXT_FALLBACK;
18740
+ }
18741
+ if (typeof historianModelOverride === "string" && historianModelOverride.trim() !== "") {
18742
+ console.warn(`[magic-context] historian.model "${historianModelOverride}" lacks provider prefix ("provider/model-id"); using fallback chain for chunk-budget derivation.`);
18743
+ }
18744
+ const chain = AGENT_MODEL_REQUIREMENTS[HISTORIAN_AGENT]?.fallbackChain;
18745
+ if (!chain || chain.length === 0)
18746
+ return DEFAULT_HISTORIAN_CONTEXT_FALLBACK;
18747
+ const expanded = expandFallbackChain(chain);
18748
+ let minLimit;
18749
+ for (const key of expanded) {
18750
+ const [providerID, ...rest] = key.split("/");
18751
+ const modelID = rest.join("/");
18752
+ if (!providerID || !modelID)
18753
+ continue;
18754
+ const limit = getModelsDevContextLimit(providerID, modelID);
18755
+ if (typeof limit !== "number" || limit <= 0)
18756
+ continue;
18757
+ if (minLimit === undefined || limit < minLimit)
18758
+ minLimit = limit;
18759
+ }
18760
+ return minLimit ?? DEFAULT_HISTORIAN_CONTEXT_FALLBACK;
18761
+ }
18762
+ 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;
18763
+ var init_derive_budgets = __esm(() => {
18764
+ init_model_requirements();
18765
+ init_models_dev_cache();
18766
+ });
18767
+
18569
18768
  // src/features/magic-context/compaction-marker.ts
18570
18769
  import { Database as Database4 } from "bun:sqlite";
18571
18770
  import { join as join12 } from "path";
@@ -19980,7 +20179,7 @@ async function runCompartmentAgent(deps) {
19980
20179
  client,
19981
20180
  db,
19982
20181
  sessionId,
19983
- tokenBudget,
20182
+ historianChunkTokens,
19984
20183
  directory,
19985
20184
  historianTimeoutMs,
19986
20185
  getNotificationParams
@@ -20015,7 +20214,7 @@ No new compartments or facts were written. Rebuild or clear the broken compartme
20015
20214
  if (protectedTailStart <= offset) {
20016
20215
  return;
20017
20216
  }
20018
- const chunk = readSessionChunk(sessionId, tokenBudget, offset, protectedTailStart);
20217
+ const chunk = readSessionChunk(sessionId, historianChunkTokens, offset, protectedTailStart);
20019
20218
  if (!chunk.text || chunk.messageCount === 0) {
20020
20219
  return;
20021
20220
  }
@@ -20158,7 +20357,7 @@ async function executeContextRecompInternal(deps) {
20158
20357
  client,
20159
20358
  db,
20160
20359
  sessionId,
20161
- tokenBudget,
20360
+ historianChunkTokens,
20162
20361
  directory,
20163
20362
  historianTimeoutMs,
20164
20363
  getNotificationParams
@@ -20212,7 +20411,7 @@ No eligible raw history exists before the protected tail, so nothing was rebuilt
20212
20411
  let candidateFacts = existingStaging?.facts ?? [];
20213
20412
  let offset = existingStaging ? existingStaging.lastEndMessage + 1 : 1;
20214
20413
  let passCount = existingStaging?.passCount ?? 0;
20215
- let currentTokenBudget = tokenBudget;
20414
+ let currentTokenBudget = historianChunkTokens;
20216
20415
  let passAttempt = 1;
20217
20416
  const resumed = existingStaging !== null;
20218
20417
  if (resumed) {
@@ -20310,7 +20509,7 @@ Nothing was written.`;
20310
20509
  ];
20311
20510
  candidateFacts = validatedPass.facts ?? [];
20312
20511
  passCount += 1;
20313
- currentTokenBudget = tokenBudget;
20512
+ currentTokenBudget = historianChunkTokens;
20314
20513
  passAttempt = 1;
20315
20514
  saveRecompStagingPass(db, sessionId, passCount, candidateCompartments, candidateFacts);
20316
20515
  const nextOffset = (validatedPass.compartments?.[validatedPass.compartments.length - 1]?.endMessage ?? chunk.endIndex) + 1;
@@ -28510,7 +28709,7 @@ Check verifiable memories against actual repository state. Update stale wording,
28510
28709
  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
28710
 
28512
28711
  ### Verification examples
28513
- - Memory: "compartment_token_budget defaults to 20000" \u2192 grep schema for \`compartment_token_budget\`, check \`.default(...)\`
28712
+ - Memory: "history_budget_percentage defaults to 0.15" \u2192 grep schema for \`history_budget_percentage\`, check \`.default(...)\`
28514
28713
  - Memory: "Durable state lives in ~/.local/share/opencode/storage/plugin/magic-context/context.db" \u2192 check storage-db.ts for the path construction
28515
28714
  - Memory: "ctx_search searches memories, facts, and history" \u2192 grep for ctx_search tool definition and unified search implementation
28516
28715
 
@@ -30239,110 +30438,13 @@ function createCompactionHandler() {
30239
30438
  }
30240
30439
  };
30241
30440
  }
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
30441
  // src/hooks/magic-context/event-resolvers.ts
30442
+ init_models_dev_cache();
30335
30443
  var DEFAULT_CONTEXT_LIMIT = 128000;
30336
- function resolveContextLimit(providerID, modelID, config2) {
30444
+ function resolveContextLimit(providerID, modelID) {
30337
30445
  if (!providerID) {
30338
30446
  return DEFAULT_CONTEXT_LIMIT;
30339
30447
  }
30340
- if (modelID) {
30341
- const modelSpecific = config2.modelContextLimitsCache?.get(`${providerID}/${modelID}`);
30342
- if (typeof modelSpecific === "number" && modelSpecific > 0) {
30343
- return modelSpecific;
30344
- }
30345
- }
30346
30448
  if (modelID) {
30347
30449
  const modelsDevLimit = getModelsDevContextLimit(providerID, modelID);
30348
30450
  if (modelsDevLimit !== undefined) {
@@ -30625,7 +30727,6 @@ init_storage_tags();
30625
30727
  init_logger();
30626
30728
 
30627
30729
  // src/hooks/magic-context/compartment-trigger.ts
30628
- init_magic_context();
30629
30730
  init_compartment_storage();
30630
30731
  init_storage();
30631
30732
  init_logger();
@@ -30703,7 +30804,7 @@ var TAIL_INFO_DEFAULTS = {
30703
30804
  tokenEstimate: 0,
30704
30805
  commitClusterCount: 0
30705
30806
  };
30706
- function getUnsummarizedTailInfo(db, sessionId, compartmentTokenBudget) {
30807
+ function getUnsummarizedTailInfo(db, sessionId, triggerBudget) {
30707
30808
  return withRawSessionMessageCache(() => {
30708
30809
  try {
30709
30810
  const lastCompartmentEnd = getLastCompartmentEndMessage(db, sessionId);
@@ -30714,7 +30815,7 @@ function getUnsummarizedTailInfo(db, sessionId, compartmentTokenBudget) {
30714
30815
  if (!hasEligibleHistory) {
30715
30816
  return { ...TAIL_INFO_DEFAULTS, nextStartOrdinal };
30716
30817
  }
30717
- const scanBudget = Math.max(MIN_PROACTIVE_TAIL_TOKEN_ESTIMATE, compartmentTokenBudget * TAIL_SIZE_TRIGGER_MULTIPLIER);
30818
+ const scanBudget = Math.max(MIN_PROACTIVE_TAIL_TOKEN_ESTIMATE, triggerBudget * TAIL_SIZE_TRIGGER_MULTIPLIER);
30718
30819
  const chunk = readSessionChunk(sessionId, scanBudget, nextStartOrdinal, protectedTailStart);
30719
30820
  const isMeaningful = chunk.hasMore || chunk.tokenEstimate >= MIN_PROACTIVE_TAIL_TOKEN_ESTIMATE || chunk.messageCount >= MIN_PROACTIVE_TAIL_MESSAGE_COUNT;
30720
30821
  return {
@@ -30730,11 +30831,11 @@ function getUnsummarizedTailInfo(db, sessionId, compartmentTokenBudget) {
30730
30831
  }
30731
30832
  });
30732
30833
  }
30733
- function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPercentage, executeThresholdPercentage, compartmentTokenBudget = DEFAULT_COMPARTMENT_TOKEN_BUDGET, autoDropToolAge, protectedTagCount, clearReasoningAge, dropToolStructure = true, commitClusterTrigger) {
30834
+ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPercentage, executeThresholdPercentage, triggerBudget, autoDropToolAge, protectedTagCount, clearReasoningAge, dropToolStructure = true, commitClusterTrigger) {
30734
30835
  if (sessionMeta.compartmentInProgress) {
30735
30836
  return { shouldFire: false };
30736
30837
  }
30737
- const tailInfo = getUnsummarizedTailInfo(db, sessionId, compartmentTokenBudget);
30838
+ const tailInfo = getUnsummarizedTailInfo(db, sessionId, triggerBudget);
30738
30839
  if (!tailInfo.hasNewRawHistory) {
30739
30840
  return { shouldFire: false };
30740
30841
  }
@@ -30750,12 +30851,12 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
30750
30851
  }
30751
30852
  const clusterEnabled = commitClusterTrigger?.enabled ?? true;
30752
30853
  const minClusters = commitClusterTrigger?.min_clusters ?? DEFAULT_MIN_COMMIT_CLUSTERS_FOR_TRIGGER;
30753
- if (clusterEnabled && tailInfo.commitClusterCount >= minClusters && tailInfo.tokenEstimate >= compartmentTokenBudget) {
30854
+ if (clusterEnabled && tailInfo.commitClusterCount >= minClusters && tailInfo.tokenEstimate >= triggerBudget) {
30754
30855
  sessionLog(sessionId, `compartment trigger: commit-cluster fire \u2014 ${tailInfo.commitClusterCount} clusters (min=${minClusters}), ~${tailInfo.tokenEstimate} tokens in eligible prefix`);
30755
30856
  return { shouldFire: true, reason: "commit_clusters" };
30756
30857
  }
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`);
30858
+ if (tailInfo.tokenEstimate >= triggerBudget * TAIL_SIZE_TRIGGER_MULTIPLIER) {
30859
+ sessionLog(sessionId, `compartment trigger: tail-size fire \u2014 ~${tailInfo.tokenEstimate} tokens exceeds ${triggerBudget * TAIL_SIZE_TRIGGER_MULTIPLIER} budget threshold`);
30759
30860
  return { shouldFire: true, reason: "tail_size" };
30760
30861
  }
30761
30862
  const proactiveTriggerPercentage = getProactiveCompartmentTriggerPercentage(executeThresholdPercentage);
@@ -31099,11 +31200,15 @@ Historian recomp started. Rebuilding compartments and facts from raw session his
31099
31200
  };
31100
31201
  }
31101
31202
 
31203
+ // src/hooks/magic-context/hook.ts
31204
+ init_derive_budgets();
31205
+
31102
31206
  // src/hooks/magic-context/event-handler.ts
31103
31207
  init_storage();
31104
31208
  init_storage_meta_persisted();
31105
31209
  init_logger();
31106
31210
  init_compaction_marker_manager();
31211
+ init_derive_budgets();
31107
31212
 
31108
31213
  // src/hooks/magic-context/event-payloads.ts
31109
31214
  function getSessionProperties(properties) {
@@ -31175,7 +31280,6 @@ function getMessageRemovedInfo(properties) {
31175
31280
  init_note_nudger();
31176
31281
 
31177
31282
  // src/hooks/magic-context/transform-compartment-phase.ts
31178
- init_magic_context();
31179
31283
  init_compartment_storage();
31180
31284
  init_storage();
31181
31285
  init_logger();
@@ -31251,7 +31355,7 @@ async function runCompartmentPhase(args) {
31251
31355
  client: args.client,
31252
31356
  db: args.db,
31253
31357
  sessionId: args.sessionId,
31254
- tokenBudget: args.compartmentTokenBudget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET,
31358
+ historianChunkTokens: args.historianChunkTokens,
31255
31359
  historyBudgetTokens: args.historyBudgetTokens,
31256
31360
  historianTimeoutMs: args.historianTimeoutMs,
31257
31361
  directory: args.compartmentDirectory,
@@ -31272,7 +31376,7 @@ async function runCompartmentPhase(args) {
31272
31376
  client: args.client,
31273
31377
  db: args.db,
31274
31378
  sessionId: args.sessionId,
31275
- tokenBudget: args.compartmentTokenBudget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET,
31379
+ historianChunkTokens: args.historianChunkTokens,
31276
31380
  historyBudgetTokens: args.historyBudgetTokens,
31277
31381
  historianTimeoutMs: args.historianTimeoutMs,
31278
31382
  directory: args.compartmentDirectory,
@@ -31425,9 +31529,7 @@ function createEventHandler2(deps) {
31425
31529
  }
31426
31530
  if (hasUsageTokens) {
31427
31531
  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
- });
31532
+ const contextLimit = resolveContextLimit(info.providerID, info.modelID);
31431
31533
  const percentage = contextLimit > 0 ? totalInputTokens / contextLimit * 100 : 0;
31432
31534
  sessionLog(info.sessionID, `event message.updated: totalInputTokens=${totalInputTokens} contextLimit=${contextLimit} percentage=${percentage.toFixed(1)}%`);
31433
31535
  deps.contextUsageMap.set(info.sessionID, {
@@ -31447,7 +31549,9 @@ function createEventHandler2(deps) {
31447
31549
  const sessionMeta = getOrCreateSessionMeta(deps.db, info.sessionID);
31448
31550
  const previousPercentage = sessionMeta.lastContextPercentage;
31449
31551
  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);
31552
+ const effectiveExecuteThreshold = resolveExecuteThreshold(deps.config.execute_threshold_percentage ?? 65, modelKey, 65);
31553
+ const triggerBudget = deriveTriggerBudget(contextLimit, effectiveExecuteThreshold);
31554
+ 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
31555
  if (triggerResult.shouldFire) {
31452
31556
  sessionLog(info.sessionID, `compartment trigger: firing (reason=${triggerResult.reason})`);
31453
31557
  updateSessionMeta(deps.db, info.sessionID, {
@@ -31725,7 +31829,6 @@ function createTextCompleteHandler() {
31725
31829
  }
31726
31830
 
31727
31831
  // src/hooks/magic-context/transform.ts
31728
- init_magic_context();
31729
31832
  init_compartment_storage();
31730
31833
  init_project_identity();
31731
31834
  init_storage();
@@ -32806,6 +32909,14 @@ function isToolProtocolPart(part) {
32806
32909
  function hasToolProtocolParts(message) {
32807
32910
  return message.parts.some(isToolProtocolPart);
32808
32911
  }
32912
+ function hasThinkingBearingParts(message) {
32913
+ return message.parts.some((part) => {
32914
+ if (part === null || typeof part !== "object")
32915
+ return false;
32916
+ const p = part;
32917
+ return p.type === "thinking" || p.type === "reasoning" || p.type === "redacted_thinking";
32918
+ });
32919
+ }
32809
32920
  function isMessageDropped(message) {
32810
32921
  const textParts = message.parts.filter(isTextPart);
32811
32922
  if (textParts.length === 0)
@@ -32840,6 +32951,11 @@ function reinjectNudgeAtAnchor(messages, nudgeText, nudgePlacements, sessionId)
32840
32951
  nudgePlacements.clear(sessionId);
32841
32952
  return false;
32842
32953
  }
32954
+ if (hasThinkingBearingParts(message)) {
32955
+ sessionLog(sessionId, `nudge anchor abandoned: message ${message.info.id} now contains thinking/reasoning parts (signed, immutable)`);
32956
+ nudgePlacements.clear(sessionId);
32957
+ return false;
32958
+ }
32843
32959
  for (let j = message.parts.length - 1;j >= 0; j--) {
32844
32960
  const part = message.parts[j];
32845
32961
  if (isTextPart(part)) {
@@ -32868,6 +32984,8 @@ function appendNudgeToAssistant(messages, nudge, nudgePlacements, sessionId) {
32868
32984
  continue;
32869
32985
  if (isMessageDropped(message))
32870
32986
  continue;
32987
+ if (hasThinkingBearingParts(message))
32988
+ continue;
32871
32989
  for (let j = message.parts.length - 1;j >= 0; j--) {
32872
32990
  const part = message.parts[j];
32873
32991
  if (isTextPart(part)) {
@@ -33151,12 +33269,13 @@ function runPostTransformPhase(args) {
33151
33269
  const forceMaterialization = args.fullFeatureMode && args.contextUsage.percentage >= args.forceMaterializationPercentage;
33152
33270
  const activeCompartmentRun = args.canRunCompartments ? getActiveCompartmentRun(args.sessionId) : undefined;
33153
33271
  const compartmentRunning = args.canRunCompartments && !args.awaitedCompartmentRun && activeCompartmentRun !== undefined;
33154
- const shouldReadPendingOps = isExplicitFlush || args.schedulerDecision === "execute" || compartmentRunning;
33272
+ const emergencyBypassCompartmentGate = forceMaterialization;
33273
+ const shouldReadPendingOps = isExplicitFlush || args.schedulerDecision === "execute" || forceMaterialization || compartmentRunning;
33155
33274
  const pendingOps = shouldReadPendingOps ? getPendingOps(args.db, args.sessionId) : [];
33156
33275
  const hasPendingUserOps = pendingOps.length > 0;
33157
- const shouldApplyPendingOps = (args.schedulerDecision === "execute" || isExplicitFlush) && !compartmentRunning;
33276
+ const shouldApplyPendingOps = (args.schedulerDecision === "execute" || isExplicitFlush || forceMaterialization) && (!compartmentRunning || emergencyBypassCompartmentGate);
33158
33277
  const isCacheBustingPass = isExplicitFlush || shouldApplyPendingOps;
33159
- const shouldRunHeuristics = args.fullFeatureMode && !compartmentRunning && (isExplicitFlush || forceMaterialization || args.schedulerDecision === "execute" && !alreadyRanThisTurn);
33278
+ const shouldRunHeuristics = args.fullFeatureMode && (!compartmentRunning || emergencyBypassCompartmentGate) && (isExplicitFlush || forceMaterialization || args.schedulerDecision === "execute" && !alreadyRanThisTurn);
33160
33279
  if (shouldRunHeuristics) {
33161
33280
  const reason = isExplicitFlush ? "explicit_flush" : forceMaterialization ? `force_materialization (${args.contextUsage.percentage.toFixed(1)}% >= ${args.forceMaterializationPercentage}%)` : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
33162
33281
  sessionLog(args.sessionId, `heuristics WILL RUN \u2014 reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=${args.currentTurnId}`);
@@ -33165,7 +33284,11 @@ function runPostTransformPhase(args) {
33165
33284
  sessionLog(args.sessionId, `transform: skipping heuristics (already ran for turn ${args.currentTurnId})`);
33166
33285
  }
33167
33286
  if (compartmentRunning && hasPendingUserOps) {
33168
- sessionLog(args.sessionId, "transform: deferring pending ops \u2014 compartment agent in progress");
33287
+ if (emergencyBypassCompartmentGate) {
33288
+ sessionLog(args.sessionId, `transform: emergency bypass \u2014 applying ${pendingOps.length} pending ops while compartment agent runs (${args.contextUsage.percentage.toFixed(1)}%)`);
33289
+ } else {
33290
+ sessionLog(args.sessionId, "transform: deferring pending ops \u2014 compartment agent in progress");
33291
+ }
33169
33292
  }
33170
33293
  try {
33171
33294
  if (shouldApplyPendingOps) {
@@ -33524,7 +33647,7 @@ function createTransform(deps) {
33524
33647
  client: deps.client,
33525
33648
  db,
33526
33649
  sessionId,
33527
- tokenBudget: deps.compartmentTokenBudget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET,
33650
+ historianChunkTokens: deps.getHistorianChunkTokens?.() ?? 20000,
33528
33651
  historyBudgetTokens,
33529
33652
  historianTimeoutMs: deps.historianTimeoutMs,
33530
33653
  directory: compartmentDirectory,
@@ -33645,7 +33768,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
33645
33768
  db,
33646
33769
  sessionId,
33647
33770
  resolvedSessionId,
33648
- compartmentTokenBudget: deps.compartmentTokenBudget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET,
33771
+ historianChunkTokens: deps.getHistorianChunkTokens?.() ?? 20000,
33649
33772
  historyBudgetTokens,
33650
33773
  historianTimeoutMs: deps.historianTimeoutMs,
33651
33774
  compartmentDirectory,
@@ -34352,6 +34475,7 @@ function createMagicContextHook(deps) {
34352
34475
  const projectPath = resolveProjectIdentity(deps.directory);
34353
34476
  registerDreamProjectDirectory(projectPath, deps.directory);
34354
34477
  let lastScheduleCheckMs = 0;
34478
+ const getHistorianChunkTokens = () => deriveHistorianChunkTokens(resolveHistorianContextLimit(deps.config.historian?.model));
34355
34479
  const nudgePlacements = createNudgePlacementStore(db);
34356
34480
  const flushedSessions = new Set;
34357
34481
  const lastHeuristicsTurnId = new Map;
@@ -34389,7 +34513,7 @@ function createMagicContextHook(deps) {
34389
34513
  enabled: deps.config.memory.enabled,
34390
34514
  injectionBudgetTokens: deps.config.memory.injection_budget_tokens
34391
34515
  } : undefined,
34392
- compartmentTokenBudget: deps.config.compartment_token_budget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET,
34516
+ getHistorianChunkTokens,
34393
34517
  historyBudgetPercentage: deps.config.history_budget_percentage,
34394
34518
  executeThresholdPercentage: deps.config.execute_threshold_percentage,
34395
34519
  historianTimeoutMs: deps.config.historian_timeout_ms ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
@@ -34470,7 +34594,7 @@ function createMagicContextHook(deps) {
34470
34594
  client: deps.client,
34471
34595
  db,
34472
34596
  sessionId,
34473
- tokenBudget: deps.config.compartment_token_budget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET,
34597
+ historianChunkTokens: getHistorianChunkTokens(),
34474
34598
  historianTimeoutMs: deps.config.historian_timeout_ms ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
34475
34599
  directory: deps.directory,
34476
34600
  fallbackModelId: (() => {
@@ -34596,7 +34720,7 @@ function createSessionHooks(args) {
34596
34720
  clear_reasoning_age: pluginConfig.clear_reasoning_age,
34597
34721
  iteration_nudge_threshold: pluginConfig.iteration_nudge_threshold,
34598
34722
  execute_threshold_percentage: pluginConfig.execute_threshold_percentage ?? DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE,
34599
- compartment_token_budget: pluginConfig.compartment_token_budget,
34723
+ historian: pluginConfig.historian,
34600
34724
  history_budget_percentage: pluginConfig.history_budget_percentage,
34601
34725
  historian_timeout_ms: pluginConfig.historian_timeout_ms,
34602
34726
  memory: pluginConfig.memory,
@@ -34921,17 +35045,18 @@ function registerRpcHandlers(rpcServer, args) {
34921
35045
  return { ok: false, error: "no session" };
34922
35046
  const { executeContextRecomp: executeContextRecomp2 } = await Promise.resolve().then(() => (init_compartment_runner(), exports_compartment_runner));
34923
35047
  const { sendIgnoredMessage: sendIgnoredMessage2 } = await Promise.resolve().then(() => (init_send_session_notification(), exports_send_session_notification));
35048
+ const { deriveHistorianChunkTokens: deriveHistorianChunkTokens2, resolveHistorianContextLimit: resolveHistorianContextLimit2 } = await Promise.resolve().then(() => (init_derive_budgets(), exports_derive_budgets));
34924
35049
  const db = getDb();
34925
35050
  if (!db)
34926
35051
  return { ok: false, error: "db unavailable" };
34927
- const DEFAULT_COMPARTMENT_TOKEN_BUDGET2 = 20000;
34928
35052
  const DEFAULT_HISTORIAN_TIMEOUT_MS2 = 600000;
35053
+ const historianChunkTokens = deriveHistorianChunkTokens2(resolveHistorianContextLimit2(config2.historian?.model));
34929
35054
  log(`[rpc] recomp requested for session ${sessionId}`);
34930
35055
  executeContextRecomp2({
34931
35056
  client: args.client,
34932
35057
  db,
34933
35058
  sessionId,
34934
- tokenBudget: config2.compartment_token_budget ?? DEFAULT_COMPARTMENT_TOKEN_BUDGET2,
35059
+ historianChunkTokens,
34935
35060
  historianTimeoutMs: config2.historian_timeout_ms ?? DEFAULT_HISTORIAN_TIMEOUT_MS2,
34936
35061
  directory,
34937
35062
  getNotificationParams: () => getNotificationParams(sessionId)
@@ -36136,6 +36261,7 @@ init_conflict_detector();
36136
36261
  init_data_path();
36137
36262
  init_logger();
36138
36263
  init_model_requirements();
36264
+ init_models_dev_cache();
36139
36265
 
36140
36266
  // src/shared/rpc-server.ts
36141
36267
  init_logger();
@@ -36144,11 +36270,11 @@ import { createServer } from "http";
36144
36270
  import { dirname } from "path";
36145
36271
 
36146
36272
  // src/shared/rpc-utils.ts
36147
- import { createHash } from "crypto";
36273
+ import { createHash as createHash2 } from "crypto";
36148
36274
  import { join as join15 } from "path";
36149
36275
  function projectHash(directory) {
36150
36276
  const normalized = directory.replace(/\/+$/, "");
36151
- return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
36277
+ return createHash2("sha256").update(normalized).digest("hex").slice(0, 16);
36152
36278
  }
36153
36279
  function rpcPortFilePath(storageDir, directory) {
36154
36280
  return join15(storageDir, "rpc", projectHash(directory), "port");
@@ -36333,6 +36459,10 @@ var plugin = async (ctx) => {
36333
36459
  rpcServer.start().catch((err) => {
36334
36460
  log(`[magic-context] RPC server failed to start: ${err}`);
36335
36461
  });
36462
+ refreshModelLimitsFromApi(ctx.client);
36463
+ setInterval(() => {
36464
+ refreshModelLimitsFromApi(ctx.client);
36465
+ }, 5 * 60 * 1000);
36336
36466
  }
36337
36467
  if (conflictResult?.hasConflict) {
36338
36468
  sendConflictWarning(ctx.client, ctx.directory, conflictResult);