@cortexkit/opencode-magic-context 0.27.2 → 0.28.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 (67) hide show
  1. package/README.md +1 -7
  2. package/dist/agents/language-directive.d.ts +27 -0
  3. package/dist/agents/language-directive.d.ts.map +1 -0
  4. package/dist/agents/magic-context-prompt.d.ts +1 -1
  5. package/dist/agents/magic-context-prompt.d.ts.map +1 -1
  6. package/dist/config/project-security.d.ts +1 -0
  7. package/dist/config/project-security.d.ts.map +1 -1
  8. package/dist/config/schema/magic-context.d.ts +4 -0
  9. package/dist/config/schema/magic-context.d.ts.map +1 -1
  10. package/dist/features/magic-context/dreamer/refresh-primers.d.ts +1 -0
  11. package/dist/features/magic-context/dreamer/refresh-primers.d.ts.map +1 -1
  12. package/dist/features/magic-context/dreamer/task-config.d.ts +1 -1
  13. package/dist/features/magic-context/dreamer/task-config.d.ts.map +1 -1
  14. package/dist/features/magic-context/dreamer/task-executor.d.ts +1 -0
  15. package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
  16. package/dist/features/magic-context/dreamer/task-scheduler.d.ts +1 -0
  17. package/dist/features/magic-context/dreamer/task-scheduler.d.ts.map +1 -1
  18. package/dist/features/magic-context/dreamer/verify.d.ts +1 -0
  19. package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
  20. package/dist/features/magic-context/memory/memory-migration.d.ts +1 -0
  21. package/dist/features/magic-context/memory/memory-migration.d.ts.map +1 -1
  22. package/dist/features/magic-context/sidekick/agent.d.ts +1 -0
  23. package/dist/features/magic-context/sidekick/agent.d.ts.map +1 -1
  24. package/dist/features/magic-context/storage-tags.d.ts +0 -5
  25. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  26. package/dist/features/magic-context/transform-decision-log.d.ts +1 -0
  27. package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
  28. package/dist/features/magic-context/user-memory/review-user-memories.d.ts +1 -0
  29. package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
  30. package/dist/hooks/magic-context/command-handler.d.ts +1 -0
  31. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  32. package/dist/hooks/magic-context/compartment-runner-historian.d.ts +1 -0
  33. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  34. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  35. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  36. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/compartment-runner-types.d.ts +1 -0
  38. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  39. package/dist/hooks/magic-context/compartment-runner-validation.d.ts +1 -1
  40. package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
  41. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts +13 -3
  42. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts.map +1 -1
  43. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/hook.d.ts +1 -0
  45. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  46. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +1 -0
  47. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/system-prompt-hash.d.ts +2 -0
  49. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +235 -57
  53. package/dist/plugin/conflict-warning-hook.d.ts.map +1 -1
  54. package/dist/plugin/dream-timer.d.ts +1 -0
  55. package/dist/plugin/dream-timer.d.ts.map +1 -1
  56. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  57. package/dist/shared/announcement.d.ts +1 -1
  58. package/dist/shared/announcement.d.ts.map +1 -1
  59. package/dist/shared/tui-preferences.d.ts +1 -1
  60. package/dist/tui/badge-contrast.d.ts +31 -0
  61. package/dist/tui/badge-contrast.d.ts.map +1 -0
  62. package/package.json +1 -1
  63. package/src/shared/announcement.ts +3 -6
  64. package/src/shared/tui-preferences.ts +2 -2
  65. package/src/tui/badge-contrast.test.ts +45 -0
  66. package/src/tui/badge-contrast.ts +46 -0
  67. package/src/tui/slots/sidebar-content.tsx +2 -1
package/dist/index.js CHANGED
@@ -169,6 +169,98 @@ var init_logger = __esm(() => {
169
169
  }
170
170
  });
171
171
 
172
+ // src/agents/language-directive.ts
173
+ function resolveLanguageName(language) {
174
+ const code = typeof language === "string" ? language.trim().toLowerCase() : "";
175
+ if (!/^[a-z]{2}$/.test(code))
176
+ return "";
177
+ let english;
178
+ try {
179
+ english = ENGLISH_LANGUAGE_NAMES.of(code) ?? undefined;
180
+ } catch {
181
+ return "";
182
+ }
183
+ if (!english)
184
+ return "";
185
+ let endonym;
186
+ try {
187
+ endonym = new Intl.DisplayNames([code], { type: "language", fallback: "none" }).of(code) ?? undefined;
188
+ } catch {
189
+ endonym = undefined;
190
+ }
191
+ return endonym && endonym !== english ? `${english} (${endonym})` : english;
192
+ }
193
+ function isValidLanguageCode(language) {
194
+ return resolveLanguageName(language) !== "";
195
+ }
196
+ function buildContentLanguageDirective(language, options = {}) {
197
+ const target = resolveLanguageName(language);
198
+ if (!target)
199
+ return "";
200
+ const lines = [
201
+ "## Output language",
202
+ "",
203
+ `Write human-readable prose you author in: ${target}.`,
204
+ "",
205
+ "Do not translate or rename structural tokens. Copy required output schemas exactly:",
206
+ "- XML tag names, XML attribute names, JSON keys, tool names, tool-call argument keys, enum values, booleans/null, and required sentinel strings stay in English exactly as shown.",
207
+ "- Keep code identifiers, file paths, commands, config keys, CLI flags, URLs, commit hashes, model/provider IDs, stack traces, diagnostics, and transcript role markers such as U:, A:, and TC: verbatim.",
208
+ "- Localize only free-text prose values/content: summaries, memory text, explanations, titles, observations, and answers — unless the prompt says to preserve original wording.",
209
+ "",
210
+ "These literal values must remain English when used:",
211
+ "PROJECT_RULES, ARCHITECTURE, CONSTRAINTS, CONFIG_VALUES, NAMING;",
212
+ "causal_incident, trajectory_correction;",
213
+ "feature, design, docs, release, investigation, bug, refactor, infra;",
214
+ "memory, observation; true, false; No relevant memories found.",
215
+ "",
216
+ "Preserve the required output shape. Do not add commentary outside the requested XML/JSON/tool output."
217
+ ];
218
+ if (options.preserveUserQuotes) {
219
+ lines.push("", `Preserve U: lines and directly quoted user text in their original source language; write the surrounding summary prose in ${target}.`);
220
+ }
221
+ if (options.retrospective) {
222
+ lines.push("", `Write the lesson text in ${target}; paraphrase source text and never quote the user.`);
223
+ }
224
+ return lines.join(`
225
+ `);
226
+ }
227
+ function withContentLanguageDirective(systemPrompt, language, options = {}) {
228
+ const directive = buildContentLanguageDirective(language, options);
229
+ return directive ? `${systemPrompt}
230
+
231
+ ${directive}` : systemPrompt;
232
+ }
233
+ function buildMigrationLanguageDirective(language) {
234
+ const target = resolveLanguageName(language);
235
+ if (!target)
236
+ return "";
237
+ return [
238
+ "## Output language",
239
+ "",
240
+ "Preserve each migrated memory's existing language — do NOT translate a memory just because an output language is set. When merging memories written in different languages, use the language of the clearest / source-majority memory; otherwise keep the source phrasing. Only the category re-mapping changes."
241
+ ].join(`
242
+ `);
243
+ }
244
+ function withMigrationLanguageDirective(systemPrompt, language) {
245
+ const directive = buildMigrationLanguageDirective(language);
246
+ return directive ? `${systemPrompt}
247
+
248
+ ${directive}` : systemPrompt;
249
+ }
250
+ function buildPrimaryLanguageDirective(language) {
251
+ const target = resolveLanguageName(language);
252
+ if (!target)
253
+ return "";
254
+ return `Use ${target} for your natural-language replies to the user unless the user explicitly asks for another language. Keep code, identifiers, file paths, commands, logs, and quoted text verbatim.`;
255
+ }
256
+ var ENGLISH_LANGUAGE_NAMES;
257
+ var init_language_directive = __esm(() => {
258
+ ENGLISH_LANGUAGE_NAMES = new Intl.DisplayNames(["en"], {
259
+ type: "language",
260
+ fallback: "none"
261
+ });
262
+ });
263
+
172
264
  // src/shared/jsonc-parser.ts
173
265
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
174
266
  function stripJsonComments(content) {
@@ -15155,6 +15247,7 @@ function defaultTaskConfig(task) {
15155
15247
  var DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65, EXECUTE_THRESHOLD_CAP_MESSAGE = "execute_threshold is capped at 80% for cache safety: a single large agent step can overflow the context window before Magic Context can compact between turns, forcing OpenCode's native compaction (hard to recover from). 80% also leaves headroom below the 85%/95% emergency bands. Use a value between 20 and 80.", DEFAULT_HISTORIAN_TIMEOUT_MS = 300000, DEFAULT_HISTORY_BUDGET_PERCENTAGE = 0.15, DEFAULT_LOCAL_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2", DreamingTaskSchema, PiThinkingLevelSchema, CronScheduleSchema, DreamTaskBaseConfigSchema, PromotionThresholdSchema, PrimerPromotionThresholdSchema, DreamTaskConfigSchema, ReviewUserMemoriesTaskConfigSchema, PromotePrimersTaskConfigSchema, DEFAULT_TASK_SCHEDULES, DreamTasksSchema, DreamerConfigSchema, SidekickConfigSchema, HistorianConfigSchema, BaseEmbeddingConfigSchema, EmbeddingConfigSchema, MagicContextConfigSchema;
15156
15248
  var init_magic_context = __esm(() => {
15157
15249
  init_zod();
15250
+ init_language_directive();
15158
15251
  init_cron();
15159
15252
  init_task_registry();
15160
15253
  init_agent_overrides();
@@ -15276,6 +15369,7 @@ var init_magic_context = __esm(() => {
15276
15369
  MagicContextConfigSchema = exports_external.object({
15277
15370
  enabled: exports_external.boolean().default(true).describe("Enable magic context (default: true)"),
15278
15371
  auto_update: exports_external.boolean().optional().describe("Enable automatic npm self-update checks for the OpenCode plugin. Security: USER-only in config loader, so hostile project configs cannot suppress updates."),
15372
+ language: exports_external.string().trim().toLowerCase().refine((s) => isValidLanguageCode(s), 'language must be a 2-letter ISO 639-1 code (e.g. "tr", "es", "de")').optional().describe("Output language for Magic Context's generated content and guidance, as a " + '2-letter ISO 639-1 code (e.g. "tr", "es", "de", "ja", "pt"). When set, the ' + "historian, dreamer, sidekick, and the agent-guidance block instruct the model to " + "write its PROSE in this language while keeping all structural tokens (XML tags, " + "the five memory category names, code identifiers, file paths) in English. " + "USER-LEVEL ONLY (ignored in project config for security). Unset = today's " + "behavior (model mirrors the conversation; English scaffolding). Changing it " + "triggers one cache re-materialization; existing compartments/memories keep their " + "original language until naturally rewritten."),
15279
15373
  ctx_reduce_enabled: exports_external.boolean().default(true).describe("When false, ctx_reduce tool is hidden, all nudges disabled, and prompt guidance about ctx_reduce stripped. Heuristic cleanup, compartments, memory, and other features still work. (default: true)"),
15280
15374
  historian: HistorianConfigSchema.describe("Historian agent configuration (model, fallback_models, variant, temperature, maxTokens, permission, two_pass, etc.)"),
15281
15375
  dreamer: DreamerConfigSchema.optional().describe("Dreamer agent + scheduling configuration (model, fallback_models, disable, schedule, tasks, etc.)"),
@@ -151003,7 +151097,7 @@ function enforceSchemaFence(db, dbPath, latestSupportedVersion) {
151003
151097
  return true;
151004
151098
  }
151005
151099
  lastSchemaFenceRejection = { persistedVersion, supportedVersion: latestSupportedVersion };
151006
- log(`[magic-context] storage fatal: refusing to open ${dbPath}; database schema v${persistedVersion} is newer than this binary supports (max v${latestSupportedVersion}). Upgrade Magic Context/OpenCode/Pi before writing to this cache.`);
151100
+ log(`[magic-context] storage fatal: refusing to open ${dbPath}; database schema v${persistedVersion} is newer than this binary supports (max v${latestSupportedVersion}). A pinned or stale plugin is likely sharing this database with a newer instance; update or unpin Magic Context with 'npx @cortexkit/magic-context@latest doctor --force', then restart.`);
151007
151101
  return false;
151008
151102
  }
151009
151103
  function setSqlitePragmaConfig(config2) {
@@ -155736,10 +155830,18 @@ function getOldestActiveUnprotectedToolTags(db, sessionId, protectedTags = 0, li
155736
155830
  WHERE session_id = ? AND status = 'active'
155737
155831
  ORDER BY tag_number DESC LIMIT 1 OFFSET ?
155738
155832
  )` : "";
155739
- const params = protectedTags > 0 ? [sessionId, sessionId, protectedTags - 1, boundedLimit] : [sessionId, boundedLimit];
155833
+ const excludeStateTools = RECLAIM_HINT_EXCLUDED_LIST ? `AND (tool_name IS NULL OR tool_name NOT IN (${RECLAIM_HINT_EXCLUDED_LIST}))` : "";
155834
+ const valueFloor = `AND (
155835
+ (token_count IS NULL AND input_token_count IS NULL)
155836
+ OR (COALESCE(token_count, 0) + COALESCE(input_token_count, 0)) >= ?
155837
+ )`;
155838
+ const params = protectedTags > 0 ? [sessionId, RECLAIM_HINT_MIN_TOKENS, sessionId, protectedTags - 1, boundedLimit] : [sessionId, RECLAIM_HINT_MIN_TOKENS, boundedLimit];
155740
155839
  const rows = db.prepare(`SELECT tag_number, tool_name
155741
155840
  FROM tags
155742
- WHERE session_id = ? AND status = 'active' AND type = 'tool' ${whereProtected}
155841
+ WHERE session_id = ? AND status = 'active' AND type = 'tool'
155842
+ ${excludeStateTools}
155843
+ ${valueFloor}
155844
+ ${whereProtected}
155743
155845
  ORDER BY tag_number ASC, id ASC
155744
155846
  LIMIT ?`).all(...params);
155745
155847
  return rows.filter((row) => typeof row.tag_number === "number").map((row) => ({
@@ -156129,7 +156231,7 @@ function deleteToolTagsByOwner(db, sessionId, ownerMsgId) {
156129
156231
  const result = getDeleteToolTagsByOwnerStatement(db).run(sessionId, ownerMsgId);
156130
156232
  return result.changes ?? 0;
156131
156233
  }
156132
- var insertTagStatements, updateTagStatusStatements, updateTagDropModeStatements, updateTagMessageIdStatements, getTagNumbersByMessageIdStatements, deleteTagsByMessageIdStatements, getMaxTagNumberBySessionStatements, getTagNumberByMessageIdStatements, updateTagByteSizeStatements, updateTagInputByteSizeStatements, CONTENT_ID_SUFFIX, updateTagTokenCountStatements, updateTagInputTokenCountStatements, getOwnerScopedToolTagNumbersStatements, getMinMessageTagNumberForRawIdStatements, TAGGER_FLOOR_SCAN_MESSAGES = 8, TAGGER_FLOOR_MAX_PROBES = 64, TAGGER_FLOOR_SAFETY_MARGIN = 256, TAGGER_FLOOR_PER_SKIP_MARGIN = 64, TAG_SELECT_COLUMNS = "id, message_id, type, status, drop_mode, tool_name, input_byte_size, byte_size, reasoning_byte_size, session_id, tag_number, caveman_depth, tool_owner_message_id", getActiveTagsBySessionStatements, getMaxDroppedTagNumberStatements, getToolTagNumberByOwnerStatements, getNullOwnerToolTagStatements, adoptNullOwnerToolTagStatements, deleteToolTagsByOwnerStatements;
156234
+ var insertTagStatements, updateTagStatusStatements, updateTagDropModeStatements, updateTagMessageIdStatements, getTagNumbersByMessageIdStatements, deleteTagsByMessageIdStatements, getMaxTagNumberBySessionStatements, getTagNumberByMessageIdStatements, updateTagByteSizeStatements, updateTagInputByteSizeStatements, CONTENT_ID_SUFFIX, RECLAIM_HINT_EXCLUDED_TOOLS, RECLAIM_HINT_MIN_TOKENS = 250, RECLAIM_HINT_EXCLUDED_LIST, updateTagTokenCountStatements, updateTagInputTokenCountStatements, getOwnerScopedToolTagNumbersStatements, getMinMessageTagNumberForRawIdStatements, TAGGER_FLOOR_SCAN_MESSAGES = 8, TAGGER_FLOOR_MAX_PROBES = 64, TAGGER_FLOOR_SAFETY_MARGIN = 256, TAGGER_FLOOR_PER_SKIP_MARGIN = 64, TAG_SELECT_COLUMNS = "id, message_id, type, status, drop_mode, tool_name, input_byte_size, byte_size, reasoning_byte_size, session_id, tag_number, caveman_depth, tool_owner_message_id", getActiveTagsBySessionStatements, getMaxDroppedTagNumberStatements, getToolTagNumberByOwnerStatements, getNullOwnerToolTagStatements, adoptNullOwnerToolTagStatements, deleteToolTagsByOwnerStatements;
156133
156235
  var init_storage_tags = __esm(() => {
156134
156236
  insertTagStatements = new WeakMap;
156135
156237
  updateTagStatusStatements = new WeakMap;
@@ -156142,6 +156244,8 @@ var init_storage_tags = __esm(() => {
156142
156244
  updateTagByteSizeStatements = new WeakMap;
156143
156245
  updateTagInputByteSizeStatements = new WeakMap;
156144
156246
  CONTENT_ID_SUFFIX = /:(?:p|file)\d+$/;
156247
+ RECLAIM_HINT_EXCLUDED_TOOLS = ["todowrite"];
156248
+ RECLAIM_HINT_EXCLUDED_LIST = RECLAIM_HINT_EXCLUDED_TOOLS.map((name2) => `'${name2.replace(/'/g, "''")}'`).join(", ");
156145
156249
  updateTagTokenCountStatements = new WeakMap;
156146
156250
  updateTagInputTokenCountStatements = new WeakMap;
156147
156251
  getOwnerScopedToolTagNumbersStatements = new WeakMap;
@@ -165579,8 +165683,13 @@ async function sendSchemaFenceWarning(client, directory, detail) {
165579
165683
  `newer build (OpenCode and Pi share one database). This build only supports`,
165580
165684
  `up to v${detail.supportedVersion}, so it has fail-closed to avoid corrupting the cache.`,
165581
165685
  "",
165582
- "Update Magic Context on this harness (or update OpenCode/Pi) to the latest",
165583
- "version, then restart. Your data is safe nothing is disabled permanently."
165686
+ "This usually means a pinned or stale plugin is sharing the database with a",
165687
+ "newer instance. Update or unpin Magic Context on this harness (or update",
165688
+ "OpenCode/Pi) to the latest version, then restart. The fastest fix is:",
165689
+ "",
165690
+ " npx @cortexkit/magic-context@latest doctor --force",
165691
+ "",
165692
+ "Your data is safe; nothing is disabled permanently."
165584
165693
  ].join(`
165585
165694
  `);
165586
165695
  try {
@@ -165850,7 +165959,7 @@ __export(exports_task_config, {
165850
165959
  dreamTaskScheduled: () => dreamTaskScheduled,
165851
165960
  buildDreamTaskRuntimeConfigs: () => buildDreamTaskRuntimeConfigs
165852
165961
  });
165853
- function buildDreamTaskRuntimeConfigs(dreamer) {
165962
+ function buildDreamTaskRuntimeConfigs(dreamer, language) {
165854
165963
  const tasks = dreamer.tasks ?? {};
165855
165964
  return CANONICAL_DREAM_TASKS.map((task) => {
165856
165965
  const t = tasks[task] ?? {
@@ -165866,6 +165975,7 @@ function buildDreamTaskRuntimeConfigs(dreamer) {
165866
165975
  model,
165867
165976
  fallbackModels,
165868
165977
  thinkingLevel,
165978
+ language,
165869
165979
  timeoutMinutes: t.timeout_minutes ?? 20,
165870
165980
  promotionThreshold: t.promotion_threshold
165871
165981
  };
@@ -177218,8 +177328,8 @@ function buildHistorianFailureNotice(failureCount, lastError) {
177218
177328
  ].join(`
177219
177329
  `);
177220
177330
  }
177221
- function buildHistorianRepairPrompt(originalPrompt, previousOutput, validationError) {
177222
- return [
177331
+ function buildHistorianRepairPrompt(originalPrompt, previousOutput, validationError, language) {
177332
+ const prompt = [
177223
177333
  originalPrompt,
177224
177334
  "",
177225
177335
  "Your previous XML response was invalid and cannot be persisted.",
@@ -177232,6 +177342,7 @@ function buildHistorianRepairPrompt(originalPrompt, previousOutput, validationEr
177232
177342
  previousOutput
177233
177343
  ].join(`
177234
177344
  `);
177345
+ return withContentLanguageDirective(prompt, language, { preserveUserQuotes: true });
177235
177346
  }
177236
177347
  function validateStoredCompartments(compartments) {
177237
177348
  if (compartments.length === 0) {
@@ -177308,6 +177419,7 @@ function getReducedRecompTokenBudget(currentBudget) {
177308
177419
  }
177309
177420
  var MIN_RECOMP_CHUNK_TOKEN_BUDGET = 20, HISTORIAN_PERSISTENT_FAILURE_THRESHOLD = 3;
177310
177421
  var init_compartment_runner_validation = __esm(async () => {
177422
+ init_language_directive();
177311
177423
  init_compartment_parser();
177312
177424
  await init_compartment_runner_mapping();
177313
177425
  });
@@ -177345,7 +177457,7 @@ async function runValidatedHistorianPass(args) {
177345
177457
  return finalResult;
177346
177458
  }
177347
177459
  await args.callbacks?.onRepairRetry?.(firstValidation.error ?? "invalid compartment output");
177348
- const repairPrompt = buildHistorianRepairPrompt(args.prompt, firstRun.result, firstValidation.error ?? "invalid compartment output");
177460
+ const repairPrompt = buildHistorianRepairPrompt(args.prompt, firstRun.result, firstValidation.error ?? "invalid compartment output", args.language);
177349
177461
  const repairRun = await runHistorianPrompt({
177350
177462
  ...args,
177351
177463
  prompt: repairPrompt,
@@ -182730,7 +182842,8 @@ ${chunkText}`,
182730
182842
  timeoutMs: historianTimeoutMs,
182731
182843
  fallbackModelId: deps.fallbackModelId,
182732
182844
  fallbackModels: deps.fallbackModels,
182733
- twoPass: deps.historianTwoPass
182845
+ twoPass: deps.historianTwoPass,
182846
+ language: deps.language
182734
182847
  });
182735
182848
  if (!validatedPass.ok) {
182736
182849
  sessionLog(sessionId, `historian failure: source=validation reason="${validatedPass.error}" chunkRange=${chunk.startIndex}-${chunk.endIndex} fallbackModel=${deps.fallbackModelId ?? "<none>"} twoPass=${deps.historianTwoPass ? "true" : "false"}`);
@@ -183206,6 +183319,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
183206
183319
  twoPass: deps.historianTwoPass,
183207
183320
  subagentKind: "recomp",
183208
183321
  agentId: HISTORIAN_RECOMP_AGENT,
183322
+ language: deps.language,
183209
183323
  callbacks: {
183210
183324
  onRepairRetry: async (error51) => {
183211
183325
  emitProgress(`Repair retry (pass ${passCount + 1})…`);
@@ -183648,6 +183762,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
183648
183762
  twoPass: deps.historianTwoPass,
183649
183763
  subagentKind: "recomp",
183650
183764
  agentId: HISTORIAN_RECOMP_AGENT,
183765
+ language: deps.language,
183651
183766
  callbacks: {
183652
183767
  onRepairRetry: async (error51) => {
183653
183768
  await sendIgnoredMessage(client, sessionId, `## Magic Recomp — Partial
@@ -186008,7 +186123,7 @@ async function runMemoryMigration(deps) {
186008
186123
  query: { directory },
186009
186124
  body: {
186010
186125
  agent: HISTORIAN_AGENT,
186011
- system: MIGRATION_SYSTEM_PROMPT,
186126
+ system: withMigrationLanguageDirective(MIGRATION_SYSTEM_PROMPT, deps.language),
186012
186127
  ...modelOverride ? { model: modelOverride } : {},
186013
186128
  parts: [{ type: "text", text: prompt, synthetic: true }]
186014
186129
  }
@@ -186087,6 +186202,7 @@ async function runMemoryMigration(deps) {
186087
186202
  }
186088
186203
  var V2_CATEGORIES, MIGRATED_BLOCK_RE, USER_OBS_BLOCK_RE, CATEGORY_BLOCK_RE = (cat) => new RegExp(`<${cat}>([\\s\\S]*?)</${cat}>`), MIGRATION_SYSTEM_PROMPT;
186089
186204
  var init_memory_migration = __esm(async () => {
186205
+ init_language_directive();
186090
186206
  init_shared();
186091
186207
  init_assistant_message_extractor();
186092
186208
  init_logger();
@@ -186203,6 +186319,7 @@ function buildRecompDeps(ctx, sessionId) {
186203
186319
  memoryEnabled: ctx.memoryEnabled,
186204
186320
  autoPromote: ctx.autoPromote,
186205
186321
  fallbackModels: ctx.fallbackModels,
186322
+ language: ctx.language,
186206
186323
  fallbackModelId: ctx.fallbackModelId ?? resolveLiveModelKey(ctx.liveSessionState, sessionId),
186207
186324
  historianTwoPass: ctx.historianTwoPass,
186208
186325
  ensureProjectRegistered: ctx.ensureProjectRegistered,
@@ -186334,7 +186451,8 @@ async function runUpgradeMemoryMigration(ctx, sessionId, migrationDirectory) {
186334
186451
  primaryModelId: ctx.fallbackModelId ?? resolveLiveModelKey(ctx.liveSessionState, sessionId),
186335
186452
  fallbackModels: ctx.fallbackModels,
186336
186453
  timeoutMs: ctx.historianTimeoutMs,
186337
- userMemoriesEnabled: ctx.userMemoriesEnabled
186454
+ userMemoriesEnabled: ctx.userMemoriesEnabled,
186455
+ language: ctx.language
186338
186456
  });
186339
186457
  return outcome.summary;
186340
186458
  } catch (error51) {
@@ -186410,15 +186528,12 @@ function shouldShowAnnouncement() {
186410
186528
  }
186411
186529
  return ordering > 0;
186412
186530
  }
186413
- var ANNOUNCEMENT_VERSION = "0.27.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
186531
+ var ANNOUNCEMENT_VERSION = "0.28.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
186414
186532
  var init_announcement = __esm(() => {
186415
186533
  init_data_path();
186416
186534
  ANNOUNCEMENT_FEATURES = [
186417
- "Dreamer V2: each maintenance task now runs on its own schedule (cron), with its own model. Configure them in setup, the dashboard, or magic-context.jsonc.",
186418
- "New 'classify' task scores each memory's importance so the most relevant stay in context as your work shifts; new 'retrospective' learns from moments you had to correct or re-explain. Both cache-safe, on by default, off anytime.",
186419
- "New Primers: durable answers to the questions that keep coming up about your project, kept current by the dreamer investigating the actual code.",
186420
- "Embedding storage no longer wipes your vectors when you change model or endpoint. Different models now coexist, so switching providers keeps your existing vectors.",
186421
- "Config moved to a shared CortexKit location (~/.config/cortexkit/ and <project>/.cortexkit/). This happens automatically on first run; your old file is preserved."
186535
+ `New 'language' option: set a top-level 2-letter language code (e.g. "tr" or "es") and Magic Context writes its summaries, memories, and guidance in that language, while keeping all structure (tags, categories, code, paths) in English. User-level only, off by default.`,
186536
+ "The ctx_reduce reminder now reflects how much tool output is actually reclaimable, instead of escalating to 'urgent' just because you're near compaction. It also no longer suggests dropping the agent's task list or tiny status outputs."
186422
186537
  ];
186423
186538
  });
186424
186539
 
@@ -186712,6 +186827,9 @@ function buildHiddenAgentConfig(prompt, allowedTools, maxSteps, overrides, agent
186712
186827
  };
186713
186828
  }
186714
186829
 
186830
+ // src/index.ts
186831
+ init_language_directive();
186832
+
186715
186833
  // src/config/index.ts
186716
186834
  init_jsonc_parser();
186717
186835
  import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
@@ -187456,6 +187574,10 @@ function stripUnsafeProjectConfigFields(projectRaw) {
187456
187574
  delete projectRaw.auto_update;
187457
187575
  warnings.push("Ignoring auto_update from project config (security: this setting only honors user-level config).");
187458
187576
  }
187577
+ if ("language" in projectRaw) {
187578
+ delete projectRaw.language;
187579
+ warnings.push("Ignoring language from project config (security: output language is a user-level setting).");
187580
+ }
187459
187581
  if ("sqlite" in projectRaw) {
187460
187582
  delete projectRaw.sqlite;
187461
187583
  warnings.push("Ignoring sqlite.* from project config (security: SQLite cache/mmap PRAGMAs apply to the " + "process-global shared database handle; only user-level config may set them).");
@@ -188288,6 +188410,9 @@ function buildDreamTaskPrompt(task, args) {
188288
188410
  // src/index.ts
188289
188411
  init_project_identity();
188290
188412
 
188413
+ // src/features/magic-context/sidekick/agent.ts
188414
+ init_language_directive();
188415
+
188291
188416
  // src/agents/sidekick.ts
188292
188417
  var SIDEKICK_AGENT = "sidekick";
188293
188418
 
@@ -188355,12 +188480,13 @@ async function runSidekick(deps) {
188355
188480
  throw error51;
188356
188481
  }
188357
188482
  const childSessionId = agentSessionId;
188483
+ const systemPrompt = withContentLanguageDirective(deps.config.system_prompt?.trim() || deps.config.prompt?.trim() || SIDEKICK_SYSTEM_PROMPT, deps.language);
188358
188484
  const sidekickRun = await promptSyncWithValidatedOutputRetry(deps.client, {
188359
188485
  path: { id: childSessionId },
188360
188486
  query: { directory: deps.sessionDirectory ?? deps.projectPath },
188361
188487
  body: {
188362
188488
  agent: SIDEKICK_AGENT,
188363
- system: deps.config.system_prompt?.trim() || deps.config.prompt?.trim() || SIDEKICK_SYSTEM_PROMPT,
188489
+ system: systemPrompt,
188364
188490
  parts: [{ type: "text", text: deps.userMessage, synthetic: true }]
188365
188491
  }
188366
188492
  }, {
@@ -189923,6 +190049,7 @@ var DREAMER_DOCS_AGENT = "dreamer-docs";
189923
190049
  var DREAMER_REVIEWER_AGENT = "dreamer-reviewer";
189924
190050
 
189925
190051
  // src/features/magic-context/dreamer/task-executor.ts
190052
+ init_language_directive();
189926
190053
  init_shared();
189927
190054
  init_assistant_message_extractor();
189928
190055
  init_logger();
@@ -189931,6 +190058,7 @@ init_memory();
189931
190058
  init_subagent_token_capture();
189932
190059
 
189933
190060
  // src/features/magic-context/user-memory/review-user-memories.ts
190061
+ init_language_directive();
189934
190062
  init_shared();
189935
190063
  init_assistant_message_extractor();
189936
190064
  init_logger();
@@ -190045,7 +190173,7 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
190045
190173
  query: { directory: args.sessionDirectory },
190046
190174
  body: {
190047
190175
  agent: DREAMER_REVIEWER_AGENT,
190048
- system: REVIEW_USER_MEMORIES_SYSTEM_PROMPT,
190176
+ system: withContentLanguageDirective(REVIEW_USER_MEMORIES_SYSTEM_PROMPT, args.language),
190049
190177
  ...modelBodyField(args.model),
190050
190178
  parts: [{ type: "text", text: prompt, synthetic: true }]
190051
190179
  }
@@ -192629,6 +192757,7 @@ async function promotePrimers(args) {
192629
192757
  }
192630
192758
 
192631
192759
  // src/features/magic-context/dreamer/refresh-primers.ts
192760
+ init_language_directive();
192632
192761
  init_read_session_formatting();
192633
192762
  init_shared();
192634
192763
  init_assistant_message_extractor();
@@ -192867,7 +192996,7 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
192867
192996
  query: { directory: args.sessionDirectory },
192868
192997
  body: {
192869
192998
  agent: DREAMER_PRIMER_INVESTIGATOR_AGENT,
192870
- system: PRIMER_INVESTIGATOR_SYSTEM_PROMPT,
192999
+ system: withContentLanguageDirective(PRIMER_INVESTIGATOR_SYSTEM_PROMPT, args.language),
192871
193000
  ...modelBodyField(args.model),
192872
193001
  parts: [{ type: "text", text: prompt, synthetic: true }]
192873
193002
  }
@@ -193103,6 +193232,7 @@ function insertDreamRun(db, run) {
193103
193232
  init_task_registry();
193104
193233
 
193105
193234
  // src/features/magic-context/dreamer/verify.ts
193235
+ init_language_directive();
193106
193236
  init_shared();
193107
193237
  init_assistant_message_extractor();
193108
193238
  init_logger();
@@ -193330,7 +193460,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
193330
193460
  query: { directory: args.sessionDirectory },
193331
193461
  body: {
193332
193462
  agent: DREAMER_MEMORY_MAPPER_AGENT,
193333
- system: VERIFY_SYSTEM_PROMPT,
193463
+ system: withContentLanguageDirective(VERIFY_SYSTEM_PROMPT, args.language),
193334
193464
  ...modelBodyField(args.model),
193335
193465
  parts: [{ type: "text", text: prompt, synthetic: true }]
193336
193466
  }
@@ -193610,7 +193740,8 @@ function createDreamTaskExecutor(deps) {
193610
193740
  deadline,
193611
193741
  promotionThreshold: config2.promotionThreshold ?? 3,
193612
193742
  model: config2.model,
193613
- fallbackModels: config2.fallbackModels
193743
+ fallbackModels: config2.fallbackModels,
193744
+ language: config2.language ?? deps.language
193614
193745
  });
193615
193746
  recordRun("completed", null);
193616
193747
  log(`[dreamer] review-user-memories: promoted=${result.promoted} merged=${result.merged} dismissed=${result.dismissed}`);
@@ -193646,7 +193777,8 @@ function createDreamTaskExecutor(deps) {
193646
193777
  deadline,
193647
193778
  forceBroad: config2.task === "verify-broad",
193648
193779
  model: config2.model,
193649
- fallbackModels: config2.fallbackModels
193780
+ fallbackModels: config2.fallbackModels,
193781
+ language: config2.language ?? deps.language
193650
193782
  });
193651
193783
  recordRun("completed", null, {
193652
193784
  memoryChanges: computeMemoryDelta(memoryBefore)
@@ -193698,6 +193830,7 @@ function createDreamTaskExecutor(deps) {
193698
193830
  deadline,
193699
193831
  model: config2.model,
193700
193832
  fallbackModels: config2.fallbackModels,
193833
+ language: config2.language ?? deps.language,
193701
193834
  rawProviderFactory: deps.primerRawProviderFactory
193702
193835
  });
193703
193836
  recordRun("completed", null);
@@ -193815,6 +193948,7 @@ function retrospectiveEventsForSessions(db, sessionIds) {
193815
193948
  try {
193816
193949
  for (const event of getCompartmentEvents(db, sessionId)) {
193817
193950
  if (event.kind !== "causal_incident" && event.kind !== "trajectory_correction") {
193951
+ log(`[dreamer] dropping event: unknown kind="${event.kind}"`);
193818
193952
  continue;
193819
193953
  }
193820
193954
  events.push({
@@ -193939,7 +194073,9 @@ async function runRetrospectiveTask(config2, ctx, helpers) {
193939
194073
  const frictionWindow = renderFrictionWindow(messages, flagged.map((message) => message.ordinal));
193940
194074
  const eventSessionIds = new Set(messages.map((message) => message.sessionId));
193941
194075
  const events = retrospectiveEventsForSessions(db, eventSessionIds);
193942
- const deepenRun = await runChildTurn(RETROSPECTIVE_SYSTEM_PROMPT, buildRetrospectivePrompt({ projectPath: projectIdentity, frictionWindow, events }));
194076
+ const deepenRun = await runChildTurn(withContentLanguageDirective(RETROSPECTIVE_SYSTEM_PROMPT, config2.language ?? deps.language, {
194077
+ retrospective: true
194078
+ }), buildRetrospectivePrompt({ projectPath: projectIdentity, frictionWindow, events }));
193943
194079
  if (leaseLost)
193944
194080
  throw new Error("Dream lease lost during retrospective");
193945
194081
  const sourceSessionId = flagged[0]?.sessionId ?? userMessages[0]?.sessionId ?? "retrospective";
@@ -194025,7 +194161,7 @@ async function runAgenticTask(config2, ctx, helpers) {
194025
194161
  query: { directory: docsDir },
194026
194162
  body: {
194027
194163
  agent: task === "maintain-docs" ? DREAMER_DOCS_AGENT : DREAMER_AGENT,
194028
- system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : CURATE_SYSTEM_PROMPT,
194164
+ system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : withContentLanguageDirective(CURATE_SYSTEM_PROMPT, config2.language ?? deps.language),
194029
194165
  ...modelBodyField(config2.model),
194030
194166
  parts: [{ type: "text", text: taskPrompt, synthetic: true }]
194031
194167
  }
@@ -194847,7 +194983,7 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
194847
194983
  }
194848
194984
  try {
194849
194985
  await runCompiledSmartNoteSweep(reg, db);
194850
- const runtimeConfigs = buildDreamTaskRuntimeConfigs(dreamerConfig);
194986
+ const runtimeConfigs = buildDreamTaskRuntimeConfigs(dreamerConfig, reg.language);
194851
194987
  const executor = createDreamTaskExecutor({
194852
194988
  client: reg.client,
194853
194989
  sessionDirectory: reg.directory,
@@ -194855,7 +194991,8 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
194855
194991
  retrospectiveRawProvider: reg.retrospectiveRawProvider ?? ((db2) => new OpenCodeRetrospectiveRawProvider({ contextDb: db2, openOpenCodeDb })),
194856
194992
  primerRawProviderFactory: reg.primerRawProviderFactory,
194857
194993
  userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreamerConfig),
194858
- ensureProjectRegistered: reg.ensureRegistered
194994
+ ensureProjectRegistered: reg.ensureRegistered,
194995
+ language: reg.language
194859
194996
  });
194860
194997
  const ran = await runDueTasksForProject({
194861
194998
  db,
@@ -196219,7 +196356,8 @@ Provide a prompt to augment with project memory context.`, {});
196219
196356
  projectPath: deps.sidekick.projectPath,
196220
196357
  sessionDirectory: deps.sidekick.sessionDirectory,
196221
196358
  userMessage: prompt,
196222
- config: deps.sidekick.config
196359
+ config: deps.sidekick.config,
196360
+ language: deps.sidekick.language
196223
196361
  });
196224
196362
  let augmentedPrompt;
196225
196363
  if (sidekickResult) {
@@ -196697,6 +196835,7 @@ var pendingPiDecisionBySession = new Map;
196697
196835
  var lastBoundMessageIdBySession = new Map;
196698
196836
  var scheduledWriteTokensBySession = new Map;
196699
196837
  var writerOverrideForTests = null;
196838
+ var retentionOverrideForTests = null;
196700
196839
  function normalizeMaterializeReason(harness, reason, rematerialized) {
196701
196840
  const raw = typeof reason === "string" ? reason.trim() : "";
196702
196841
  if (raw.length > 0) {
@@ -196797,7 +196936,7 @@ function writeTransformDecisionRow(dbPath, row) {
196797
196936
  WHERE session_id = ? AND harness = ?
196798
196937
  ORDER BY ts_ms DESC, rowid DESC
196799
196938
  LIMIT ?
196800
- )`).run(row.sessionId, row.harness, row.sessionId, row.harness, TRANSFORM_DECISIONS_RETENTION);
196939
+ )`).run(row.sessionId, row.harness, row.sessionId, row.harness, retentionOverrideForTests ?? TRANSFORM_DECISIONS_RETENTION);
196801
196940
  } finally {
196802
196941
  closeQuietly(db);
196803
196942
  }
@@ -196825,6 +196964,7 @@ function channel1RefireTokens(workingWindowTokens) {
196825
196964
  var S_GENTLE = 0.2;
196826
196965
  var S_FIRM = 0.4;
196827
196966
  var S_URGENT = 0.65;
196967
+ var CHANNEL1_PRESSURE_FLOOR = 0.8;
196828
196968
  var LEVEL_RANK = { gentle: 1, firm: 2, urgent: 3 };
196829
196969
  var DROP_SENTINELS = ["[dropped", "[truncated"];
196830
196970
  function isDroppedToolOutput(output) {
@@ -196889,7 +197029,8 @@ function computeTailTokenEstimate(messages) {
196889
197029
  };
196890
197030
  }
196891
197031
  function decideChannel1(input) {
196892
- const { undroppedTokens, pressure, workingWindowTokens, hasRecentReduce } = input;
197032
+ const { undroppedTokens, workingWindowTokens, hasRecentReduce } = input;
197033
+ const pressure = Math.min(1, Math.max(0, input.pressure));
196893
197034
  const resetCycle = hasRecentReduce || undroppedTokens < input.lastNudgeUndropped;
196894
197035
  const lastNudge = resetCycle ? 0 : input.lastNudgeUndropped;
196895
197036
  const lastLevel = resetCycle ? "" : input.lastNudgeLevel;
@@ -196904,8 +197045,10 @@ function decideChannel1(input) {
196904
197045
  return quiet();
196905
197046
  if (undroppedTokens < CHANNEL1_FLOOR_TOKENS)
196906
197047
  return quiet();
196907
- const budget = workingWindowTokens > 0 ? workingWindowTokens : undroppedTokens || 1;
196908
- const severity = undroppedTokens / budget * pressure;
197048
+ if (pressure < CHANNEL1_PRESSURE_FLOOR)
197049
+ return quiet();
197050
+ const denom = Math.max(input.estimatedInputTokens, 1);
197051
+ const severity = Math.min(1, undroppedTokens / denom);
196909
197052
  if (severity < S_GENTLE)
196910
197053
  return quiet();
196911
197054
  let level;
@@ -197206,6 +197349,7 @@ init_session_project_storage();
197206
197349
  init_storage_meta_persisted();
197207
197350
  await init_storage();
197208
197351
  init_logger();
197352
+ init_models_dev_cache();
197209
197353
 
197210
197354
  // src/hooks/magic-context/boundary-execution.ts
197211
197355
  var FORCE_MATERIALIZE_PERCENTAGE2 = 85;
@@ -201345,6 +201489,18 @@ function findLastAssistantModel2(messages) {
201345
201489
  }
201346
201490
  return null;
201347
201491
  }
201492
+ function findNewestUserModel(messages) {
201493
+ for (let i = messages.length - 1;i >= 0; i--) {
201494
+ const info = messages[i].info;
201495
+ if (info.role !== "user")
201496
+ continue;
201497
+ if (info.model?.providerID && info.model.modelID) {
201498
+ return { providerID: info.model.providerID, modelID: info.model.modelID };
201499
+ }
201500
+ return null;
201501
+ }
201502
+ return null;
201503
+ }
201348
201504
  function createTransform(deps) {
201349
201505
  const loadedSessions = new Set;
201350
201506
  const lastEmergencyNotificationCount = new Map;
@@ -201405,15 +201561,15 @@ function createTransform(deps) {
201405
201561
  const canRunCompartments = fullFeatureMode && historianRunnable && deps.client !== undefined && compartmentDirectory.length > 0;
201406
201562
  const fallbackModelId = deps.getFallbackModelId?.(sessionId);
201407
201563
  const tModelDetect = performance.now();
201564
+ const persistedUsageBeforeResets = loadPersistedUsage(db, sessionId);
201408
201565
  if (deps.liveModelBySession) {
201409
- const lastAssistantModel = findLastAssistantModel2(messages);
201410
- if (lastAssistantModel) {
201411
- const knownModel = deps.liveModelBySession.get(sessionId);
201412
- if (!knownModel) {
201413
- deps.liveModelBySession.set(sessionId, lastAssistantModel);
201414
- } else if (knownModel.providerID !== lastAssistantModel.providerID || knownModel.modelID !== lastAssistantModel.modelID) {
201415
- sessionLog(sessionId, `transform: model change detected (${knownModel.providerID}/${knownModel.modelID} -> ${lastAssistantModel.providerID}/${lastAssistantModel.modelID}), clearing stale context state`);
201416
- deps.liveModelBySession.set(sessionId, lastAssistantModel);
201566
+ const currentOutgoingModel = findNewestUserModel(messages) ?? deps.liveModelBySession.get(sessionId) ?? findLastAssistantModel2(messages);
201567
+ if (currentOutgoingModel) {
201568
+ deps.liveModelBySession.set(sessionId, currentOutgoingModel);
201569
+ const outgoingModelKey = resolveModelKey(currentOutgoingModel.providerID, currentOutgoingModel.modelID);
201570
+ const lastUsageModelKey = persistedUsageBeforeResets?.lastObservedModelKey ?? null;
201571
+ if (lastUsageModelKey != null && outgoingModelKey != null && lastUsageModelKey !== outgoingModelKey) {
201572
+ sessionLog(sessionId, `transform: model change since last usage (${lastUsageModelKey} -> ${outgoingModelKey}), clearing stale per-model state`);
201417
201573
  updateSessionMeta(db, sessionId, {
201418
201574
  lastContextPercentage: 0,
201419
201575
  lastInputTokens: 0,
@@ -201443,7 +201599,6 @@ function createTransform(deps) {
201443
201599
  const tFirstPass = performance.now();
201444
201600
  const isFirstTransformPassForSession = !loadedSessions.has(sessionId);
201445
201601
  loadedSessions.add(sessionId);
201446
- const persistedUsageBeforeFirstPassReset = loadPersistedUsage(db, sessionId);
201447
201602
  const historianFailureState = getHistorianFailureState(db, sessionId);
201448
201603
  if (isFirstTransformPassForSession && sessionMeta) {
201449
201604
  const persistedPct = sessionMeta.lastContextPercentage ?? 0;
@@ -201462,6 +201617,17 @@ function createTransform(deps) {
201462
201617
  let emergencyRecoveryArmed = false;
201463
201618
  if (fullFeatureMode) {
201464
201619
  try {
201620
+ const armModel = deps.liveModelBySession?.get(sessionId);
201621
+ const armModelKey = deps.getModelKey?.(sessionId);
201622
+ const armSnapshot = persistedUsageBeforeResets;
201623
+ const lastMeasuredInput = armSnapshot?.usage.inputTokens ?? sessionMeta?.lastInputTokens ?? 0;
201624
+ const lastMeasuredModelKey = armSnapshot?.lastObservedModelKey ?? null;
201625
+ const armCatalogLimit = armModel ? getSdkContextLimit(armModel.providerID, armModel.modelID) : undefined;
201626
+ if (!sessionMeta?.isSubagent && armModel && typeof armCatalogLimit === "number" && armCatalogLimit > 0 && lastMeasuredInput > armCatalogLimit && lastMeasuredModelKey != null && armModelKey != null && lastMeasuredModelKey !== armModelKey && !getOverflowState(db, sessionId).needsEmergencyRecovery) {
201627
+ sessionLog(sessionId, `transform: last input ${lastMeasuredInput} (model ${lastMeasuredModelKey}) exceeds new model ${armModelKey} catalog limit ${armCatalogLimit}; arming overflow recovery proactively for the shrinking switch`);
201628
+ recordOverflowDetected(db, sessionId, undefined, armModelKey);
201629
+ resetProtectedTailNoEligibleHead(db, sessionId);
201630
+ }
201465
201631
  const overflowState = getOverflowState(db, sessionId);
201466
201632
  emergencyRecoveryArmed = overflowState.needsEmergencyRecovery;
201467
201633
  if (contextUsageEarly.percentage < 80 && !overflowState.needsEmergencyRecovery) {
@@ -201498,7 +201664,7 @@ function createTransform(deps) {
201498
201664
  sessionID: sessionId
201499
201665
  }) : undefined;
201500
201666
  const currentModelKeyForBoundary = deps.getModelKey?.(sessionId);
201501
- const persistedUsageFreshForBoundary = persistedUsageBeforeFirstPassReset && Date.now() - persistedUsageBeforeFirstPassReset.updatedAt <= 10 * 60 * 1000 && (persistedUsageBeforeFirstPassReset.lastObservedModelKey === null || currentModelKeyForBoundary === undefined || persistedUsageBeforeFirstPassReset.lastObservedModelKey === currentModelKeyForBoundary) && (resolvedContextLimit === undefined || persistedUsageBeforeFirstPassReset.lastUsageContextLimit === 0 || persistedUsageBeforeFirstPassReset.lastUsageContextLimit === resolvedContextLimit) ? persistedUsageBeforeFirstPassReset.usage : null;
201667
+ const persistedUsageFreshForBoundary = persistedUsageBeforeResets && Date.now() - persistedUsageBeforeResets.updatedAt <= 10 * 60 * 1000 && (persistedUsageBeforeResets.lastObservedModelKey === null || currentModelKeyForBoundary === undefined || persistedUsageBeforeResets.lastObservedModelKey === currentModelKeyForBoundary) && (resolvedContextLimit === undefined || persistedUsageBeforeResets.lastUsageContextLimit === 0 || persistedUsageBeforeResets.lastUsageContextLimit === resolvedContextLimit) ? persistedUsageBeforeResets.usage : null;
201502
201668
  const boundaryUsageForProtectedTail = persistedUsageFreshForBoundary ?? contextUsageEarly;
201503
201669
  const boundaryUsageSource = persistedUsageFreshForBoundary ? "persisted" : "live";
201504
201670
  const historyBudgetTokens = resolveHistoryBudgetTokens(deps.historyBudgetPercentage, contextUsageEarly, deps.executeThresholdPercentage, deps.getModelKey?.(sessionId), deps.executeThresholdTokens, resolvedContextLimit);
@@ -202735,6 +202901,7 @@ function maybeInjectChannel1Nudge(args, sessionId, tool, output) {
202735
202901
  const decision = decideChannel1({
202736
202902
  undroppedTokens,
202737
202903
  pressure,
202904
+ estimatedInputTokens: state.lastInputTokens + state.turnToolTokens,
202738
202905
  workingWindowTokens,
202739
202906
  lastNudgeUndropped: getLastNudgeUndropped(args.db, sessionId),
202740
202907
  lastNudgeLevel: getLastNudgeLevel(args.db, sessionId),
@@ -202800,6 +202967,7 @@ init_send_session_notification();
202800
202967
  import { createHash as createHash13 } from "node:crypto";
202801
202968
 
202802
202969
  // src/agents/magic-context-prompt.ts
202970
+ init_language_directive();
202803
202971
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
202804
202972
 
202805
202973
  Most AI sessions are disposable: one session per task, discarded when it's done — like hiring a developer for a single bug fix and letting them go the moment they finish. Magic Context changes this completely. This session is a durable working relationship: you carry the full history and accumulated knowledge of this project, and you continue across many tasks, bugs, and features — with memory that persists across restarts. This session may continue for weeks, months, or even years.
@@ -202878,7 +203046,7 @@ Drop silently — do not narrate it. NEVER drop large ranges blindly (e.g., "1-5
202878
203046
  Older tool calls may show \`[dropped §N§]\` sentinels; that is normal context management, not a pattern to copy. ALWAYS make fresh real tool calls when you need data again; never fabricate or inline tool output.`;
202879
203047
  var CAVEMAN_COMPRESSION_WARNING = `
202880
203048
  **BEWARE**: History compression is on; older user AND assistant text — including your own earlier responses — has been deterministically rewritten in a terse caveman style (dropped articles, missing auxiliaries, \`//\` instead of connectives like \`because\`). This is automatic context compression that runs after the fact, not your actual prior wording or the user's. **DO NOT mimic this style in new turns.** Write fresh responses in normal prose. If you notice your output drifting into caveman cadence, that drift is in-context-learning bleeding from the compressed history — consciously revert to full sentences.`;
202881
- function buildMagicContextSection(_agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false) {
203049
+ function buildMagicContextSection(_agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language) {
202882
203050
  if (subagentMode) {
202883
203051
  return `## Magic Context
202884
203052
 
@@ -202890,13 +203058,17 @@ The dreamer evaluates smart note conditions during nightly runs and surfaces the
202890
203058
  Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`` : "";
202891
203059
  const temporalGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : "";
202892
203060
  const cavemanWarning = cavemanTextCompressionEnabled && !ctxReduceEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
203061
+ const languageDirective = buildPrimaryLanguageDirective(language);
203062
+ const languageGuidance = languageDirective ? `
203063
+
203064
+ ${languageDirective}` : "";
202893
203065
  if (!ctxReduceEnabled) {
202894
203066
  return `## Magic Context
202895
203067
 
202896
203068
  ${LONG_TERM_PARTNER_FRAME}
202897
203069
  ${PARTNER_FRAME_CLOSER_NO_REDUCE}
202898
203070
 
202899
- ${BASE_INTRO_NO_REDUCE()}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}`;
203071
+ ${BASE_INTRO_NO_REDUCE()}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}${languageGuidance}`;
202900
203072
  }
202901
203073
  return `## Magic Context
202902
203074
 
@@ -202906,7 +203078,7 @@ ${PARTNER_FRAME_CLOSER_REDUCE}
202906
203078
  ${BASE_INTRO(protectedTags)}${smartNoteGuidance}${temporalGuidance}
202907
203079
  ${GENERIC_SECTION}
202908
203080
 
202909
- Prefer many small targeted operations over one large blanket operation, and keep the working set tidy as routine maintenance.`;
203081
+ Prefer many small targeted operations over one large blanket operation, and keep the working set tidy as routine maintenance.${languageGuidance}`;
202910
203082
  }
202911
203083
 
202912
203084
  // src/hooks/magic-context/system-prompt-hash.ts
@@ -202964,7 +203136,7 @@ function createSystemPromptHashHandler(deps) {
202964
203136
  const fullPrompt = output.system.join(`
202965
203137
  `);
202966
203138
  if (fullPrompt.length > 0 && !fullPrompt.includes(MAGIC_CONTEXT_MARKER) && !skipGuidanceForDisabledSubagent) {
202967
- const guidance = buildMagicContextSection(null, deps.protectedTags, effectiveCtxReduceEnabled, deps.dreamerEnabled, deps.experimentalTemporalAwareness, deps.experimentalCavemanTextCompression, subagentReduceMode);
203139
+ const guidance = buildMagicContextSection(null, deps.protectedTags, effectiveCtxReduceEnabled, deps.dreamerEnabled, deps.experimentalTemporalAwareness, deps.experimentalCavemanTextCompression, subagentReduceMode, deps.language);
202968
203140
  output.system.push(guidance);
202969
203141
  sessionLog(sessionId, `injected generic guidance into system prompt (ctxReduce=${effectiveCtxReduceEnabled}, subagent=${isSubagentSession}, subagentReduceMode=${subagentReduceMode})`);
202970
203142
  }
@@ -203252,6 +203424,7 @@ function createMagicContextHook(deps) {
203252
203424
  memoryEnabled: deps.config.memory?.enabled ?? true,
203253
203425
  autoPromote: deps.config.memory?.auto_promote ?? true,
203254
203426
  fallbackModels: historianFallbackModels,
203427
+ language: deps.config.language,
203255
203428
  fallbackModelId: (() => {
203256
203429
  const model = resolveLiveModel(sessionId);
203257
203430
  return model ? `${model.providerID}/${model.modelID}` : undefined;
@@ -203290,7 +203463,7 @@ function createMagicContextHook(deps) {
203290
203463
  signal,
203291
203464
  onProgress: ({ embedded, total }) => {
203292
203465
  const cur = recompProgressBySession.get(sessionId);
203293
- if (!cur || cur.phase !== "recomp")
203466
+ if (cur?.phase !== "recomp")
203294
203467
  return;
203295
203468
  recompProgressBySession.set(sessionId, {
203296
203469
  ...cur,
@@ -203493,7 +203666,7 @@ function createMagicContextHook(deps) {
203493
203666
  return;
203494
203667
  }
203495
203668
  lastScheduleCheckMs = now;
203496
- const runtimeConfigs = buildDreamTaskRuntimeConfigs(dreaming);
203669
+ const runtimeConfigs = buildDreamTaskRuntimeConfigs(dreaming, deps.config.language);
203497
203670
  const executor = createDreamTaskExecutor({
203498
203671
  client: deps.client,
203499
203672
  sessionDirectory: deps.directory,
@@ -203502,7 +203675,8 @@ function createMagicContextHook(deps) {
203502
203675
  contextDb: providerDb,
203503
203676
  openOpenCodeDb
203504
203677
  }),
203505
- userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreaming)
203678
+ userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreaming),
203679
+ language: deps.config.language
203506
203680
  });
203507
203681
  runDueTasksForProject({
203508
203682
  db,
@@ -203551,7 +203725,8 @@ function createMagicContextHook(deps) {
203551
203725
  config: sidekickConfig,
203552
203726
  projectPath,
203553
203727
  sessionDirectory: deps.directory,
203554
- client: deps.client
203728
+ client: deps.client,
203729
+ language: deps.config.language
203555
203730
  } : undefined,
203556
203731
  dreamer: dreamerConfig ? {
203557
203732
  config: dreamerConfig,
@@ -203559,7 +203734,7 @@ function createMagicContextHook(deps) {
203559
203734
  runManual: (task) => runManualDream({
203560
203735
  db,
203561
203736
  projectIdentity: projectPath,
203562
- tasks: buildDreamTaskRuntimeConfigs(dreamerConfig),
203737
+ tasks: buildDreamTaskRuntimeConfigs(dreamerConfig, deps.config.language),
203563
203738
  executor: createDreamTaskExecutor({
203564
203739
  client: deps.client,
203565
203740
  sessionDirectory: deps.directory,
@@ -203568,7 +203743,8 @@ function createMagicContextHook(deps) {
203568
203743
  contextDb: providerDb,
203569
203744
  openOpenCodeDb
203570
203745
  }),
203571
- userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreamerConfig)
203746
+ userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreamerConfig),
203747
+ language: deps.config.language
203572
203748
  }),
203573
203749
  task
203574
203750
  })
@@ -203579,6 +203755,7 @@ function createMagicContextHook(deps) {
203579
203755
  protectedTags: deps.config.protected_tags,
203580
203756
  ctxReduceEnabled,
203581
203757
  dreamerEnabled: dreamerRunnable,
203758
+ language: deps.config.language,
203582
203759
  injectDocs: deps.config.dreamer?.inject_docs !== false,
203583
203760
  directory: deps.directory,
203584
203761
  historyRefreshSessions,
@@ -206310,6 +206487,7 @@ var server2 = async (ctx) => {
206310
206487
  projectIdentity: resolveProjectIdentity(ctx.directory),
206311
206488
  client: ctx.client,
206312
206489
  dreamerConfig: dreamerRunnable ? pluginConfig.dreamer : undefined,
206490
+ language: pluginConfig.language,
206313
206491
  embeddingConfig: pluginConfig.embedding,
206314
206492
  memoryEnabled: pluginConfig.memory?.enabled === true,
206315
206493
  gitCommitIndexing: pluginConfig.memory.git_commit_indexing?.enabled ? {
@@ -206481,9 +206659,9 @@ var server2 = async (ctx) => {
206481
206659
  const registrations = buildHiddenAgentRegistrations({
206482
206660
  dreamerPrompt: DREAMER_SYSTEM_PROMPT,
206483
206661
  smartNoteCompilerPrompt: SMART_NOTE_COMPILER_SYSTEM_PROMPT,
206484
- historianPrompt: COMPARTMENT_AGENT_SYSTEM_PROMPT,
206485
- historianRecompPrompt: COMPARTMENT_STRUCTURAL_SYSTEM_PROMPT,
206486
- historianEditorPrompt: HISTORIAN_EDITOR_SYSTEM_PROMPT,
206662
+ historianPrompt: withContentLanguageDirective(COMPARTMENT_AGENT_SYSTEM_PROMPT, pluginConfig.language, { preserveUserQuotes: true }),
206663
+ historianRecompPrompt: withContentLanguageDirective(COMPARTMENT_STRUCTURAL_SYSTEM_PROMPT, pluginConfig.language, { preserveUserQuotes: true }),
206664
+ historianEditorPrompt: withContentLanguageDirective(HISTORIAN_EDITOR_SYSTEM_PROMPT, pluginConfig.language, { preserveUserQuotes: true }),
206487
206665
  sidekickPrompt: SIDEKICK_SYSTEM_PROMPT,
206488
206666
  dreamerOverrides: dreamerAgentOverrides,
206489
206667
  historianOverrides: historianAgentOverrides,