@cortexkit/opencode-magic-context 0.27.3 → 0.29.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 (87) hide show
  1. package/README.md +26 -1
  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 +15 -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-prompts.d.ts +1 -1
  17. package/dist/features/magic-context/dreamer/task-prompts.d.ts.map +1 -1
  18. package/dist/features/magic-context/dreamer/task-scheduler.d.ts +1 -0
  19. package/dist/features/magic-context/dreamer/task-scheduler.d.ts.map +1 -1
  20. package/dist/features/magic-context/dreamer/verify.d.ts +1 -0
  21. package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
  22. package/dist/features/magic-context/memory/memory-migration.d.ts +1 -0
  23. package/dist/features/magic-context/memory/memory-migration.d.ts.map +1 -1
  24. package/dist/features/magic-context/sidekick/agent.d.ts +1 -0
  25. package/dist/features/magic-context/sidekick/agent.d.ts.map +1 -1
  26. package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts.map +1 -1
  27. package/dist/features/magic-context/storage-tags.d.ts +0 -5
  28. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  29. package/dist/features/magic-context/types.d.ts +12 -1
  30. package/dist/features/magic-context/types.d.ts.map +1 -1
  31. package/dist/features/magic-context/user-memory/review-user-memories.d.ts +1 -0
  32. package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
  33. package/dist/hooks/magic-context/apply-operations.d.ts +8 -1
  34. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  35. package/dist/hooks/magic-context/command-handler.d.ts +1 -0
  36. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/compartment-runner-historian.d.ts +1 -0
  38. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  39. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  41. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/compartment-runner-types.d.ts +1 -0
  43. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/compartment-runner-validation.d.ts +1 -1
  45. package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
  46. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts +13 -3
  47. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/edit-marker.d.ts +11 -0
  49. package/dist/hooks/magic-context/edit-marker.d.ts.map +1 -0
  50. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  51. package/dist/hooks/magic-context/hook.d.ts +2 -0
  52. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/read-session-formatting.d.ts.map +1 -1
  54. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +1 -0
  55. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  56. package/dist/hooks/magic-context/supersession-reclaim.d.ts +34 -0
  57. package/dist/hooks/magic-context/supersession-reclaim.d.ts.map +1 -0
  58. package/dist/hooks/magic-context/system-prompt-hash.d.ts +7 -0
  59. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  60. package/dist/hooks/magic-context/tag-messages.d.ts +8 -0
  61. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  62. package/dist/hooks/magic-context/tool-drop-target.d.ts +2 -0
  63. package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
  64. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +8 -0
  65. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  66. package/dist/hooks/magic-context/transform.d.ts +4 -0
  67. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +492 -80
  70. package/dist/plugin/dream-timer.d.ts +1 -0
  71. package/dist/plugin/dream-timer.d.ts.map +1 -1
  72. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  73. package/dist/plugin/tool-registry.d.ts.map +1 -1
  74. package/dist/shared/announcement.d.ts +1 -1
  75. package/dist/shared/announcement.d.ts.map +1 -1
  76. package/dist/shared/commit-detection.d.ts +29 -0
  77. package/dist/shared/commit-detection.d.ts.map +1 -0
  78. package/dist/shared/tag-transcript.d.ts.map +1 -1
  79. package/dist/shared/transcript.d.ts +15 -0
  80. package/dist/shared/transcript.d.ts.map +1 -1
  81. package/package.json +1 -1
  82. package/src/shared/announcement.ts +3 -6
  83. package/src/shared/commit-detection.test.ts +63 -0
  84. package/src/shared/commit-detection.ts +53 -0
  85. package/src/shared/tag-transcript.ts +32 -0
  86. package/src/shared/transcript-opencode.ts +33 -0
  87. package/src/shared/transcript.ts +17 -0
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.)"),
@@ -15313,6 +15407,7 @@ var init_magic_context = __esm(() => {
15313
15407
  }).describe("Embedding provider configuration"),
15314
15408
  temporal_awareness: exports_external.boolean().default(true).describe('Inject wall-clock gap markers (<!-- +Xm -->) between user messages where > 5 min elapsed since the previous message, and add start/end date attributes on compartments. Gives the agent a sense of session pacing and "how long ago" across multi-day sessions. Graduated from experimental.temporal_awareness; default: true (set false to opt out).'),
15315
15409
  keep_subagents: exports_external.boolean().default(false).describe("Debug: keep the child sessions Magic Context spawns for its own subagents (historian, dreamer, sidekick, memory-migration) instead of deleting them on success. Useful for short-term inspection/data collection — their full transcript (prompt, tool calls, token usage, output) stays in the host session store. Kept sessions accumulate until manually cleared; leave false for normal use. Requires a restart to take effect."),
15410
+ smart_drops: exports_external.boolean().default(false).describe("Content-aware reclaim of provably-superseded tool output, layered on the existing execute-pass auto-drop. When on: superseded todowrite (keep newest 1), spent ctx_reduce (keep newest 5), and zero-value meta (bash_status, bash_kill, ctx_note read/dismiss) outputs are dropped; older edits to a file are compressed to a filePath-preserving marker while the newest edit per file stays full. Only acts on passes already busting the cache, so it never originates a cache bust. Honors the protected-tag reserve. Experimental: opt-in, default off until cache stability is proven; when off the wire is byte-identical to the positional-only reclaim. Requires a restart."),
15316
15411
  caveman_text_compression: exports_external.object({
15317
15412
  enabled: exports_external.boolean().default(false).describe("Apply deterministic caveman-style text compression to old conversation text. Only active when ctx_reduce_enabled=false. Compresses user/assistant text in oldest-first tiers: ultra (oldest 20%), full, lite, untouched (newest 40%)."),
15318
15413
  min_chars: exports_external.number().min(100).max(1e4).default(500).describe("Text parts shorter than this (characters) stay untouched. Min 100, max 10000. Default: 500.")
@@ -16828,6 +16923,19 @@ var init_read_session_db = __esm(async () => {
16828
16923
  await init_sqlite();
16829
16924
  });
16830
16925
 
16926
+ // src/shared/commit-detection.ts
16927
+ function textMentionsRecentCommit(text) {
16928
+ return COMMIT_HASH_TEST_PATTERN.test(text) && COMMIT_VERB_PATTERN.test(text);
16929
+ }
16930
+ function createCommitHashExtractPattern() {
16931
+ return new RegExp(`\`?\\b(${HASH_HEX})\\b\`?`, "gi");
16932
+ }
16933
+ var HASH_HEX = "[0-9a-f]{7,12}", COMMIT_HASH_TEST_PATTERN, COMMIT_VERB_PATTERN;
16934
+ var init_commit_detection = __esm(() => {
16935
+ COMMIT_HASH_TEST_PATTERN = new RegExp(`\\b${HASH_HEX}\\b`, "i");
16936
+ COMMIT_VERB_PATTERN = /\b(?:commit(?:ted|ting|s)?|cherry-?pick(?:ed|ing|s)?|merge[ds]?|merging|rebas(?:e|ed|es|ing))\b/i;
16937
+ });
16938
+
16831
16939
  // ../../node_modules/.bun/ai-tokenizer@1.0.6/node_modules/ai-tokenizer/dist/index.js
16832
16940
  function _typeof(o) {
16833
16941
  "@babel/helpers - typeof";
@@ -149377,7 +149485,7 @@ function formatBlock(block) {
149377
149485
  function extractCommitHashes(text) {
149378
149486
  const hashes = [];
149379
149487
  const seen = new Set;
149380
- for (const match of text.matchAll(COMMIT_HASH_PATTERN)) {
149488
+ for (const match of text.matchAll(createCommitHashExtractPattern())) {
149381
149489
  const hash2 = match[1]?.toLowerCase();
149382
149490
  if (!hash2 || seen.has(hash2))
149383
149491
  continue;
@@ -149390,10 +149498,10 @@ function extractCommitHashes(text) {
149390
149498
  }
149391
149499
  function compactTextForSummary(text, role) {
149392
149500
  const commitHashes = role === "assistant" ? extractCommitHashes(text) : [];
149393
- if (commitHashes.length === 0 || !COMMIT_HINT_PATTERN.test(text)) {
149501
+ if (commitHashes.length === 0 || !COMMIT_VERB_PATTERN.test(text)) {
149394
149502
  return { text, commitHashes };
149395
149503
  }
149396
- const withoutHashes = text.replace(COMMIT_HASH_PATTERN, "").replace(/\(\s*\)/g, "").replace(/\s+,/g, ",").replace(/,\s*,+/g, ", ").replace(/\s{2,}/g, " ").replace(/\s+([,.;:])/g, "$1").trim();
149504
+ const withoutHashes = text.replace(createCommitHashExtractPattern(), "").replace(/\(\s*\)/g, "").replace(/\s+,/g, ",").replace(/,\s*,+/g, ", ").replace(/\s{2,}/g, " ").replace(/\s+([,.;:])/g, "$1").trim();
149397
149505
  return {
149398
149506
  text: withoutHashes.length > 0 ? withoutHashes : text,
149399
149507
  commitHashes
@@ -149412,12 +149520,11 @@ function mergeCommitHashes(existing, next) {
149412
149520
  }
149413
149521
  return merged;
149414
149522
  }
149415
- var COMMIT_HASH_PATTERN, COMMIT_HINT_PATTERN, MAX_COMMITS_PER_BLOCK = 5, tokenizer;
149523
+ var MAX_COMMITS_PER_BLOCK = 5, tokenizer;
149416
149524
  var init_read_session_formatting = __esm(() => {
149525
+ init_commit_detection();
149417
149526
  init_dist();
149418
149527
  init_claude();
149419
- COMMIT_HASH_PATTERN = /`?\b([0-9a-f]{6,12})\b`?/gi;
149420
- COMMIT_HINT_PATTERN = /\b(commit(?:ted)?|cherry-?pick(?:ed)?|hash(?:es)?|sha)\b/i;
149421
149528
  tokenizer = new src_default(exports_claude);
149422
149529
  });
149423
149530
 
@@ -149724,6 +149831,37 @@ var init_tag_part_guards = __esm(() => {
149724
149831
  init_tag_content_primitives();
149725
149832
  });
149726
149833
 
149834
+ // src/hooks/magic-context/edit-marker.ts
149835
+ function safeSlice(str, maxLen) {
149836
+ if (str.length <= maxLen)
149837
+ return str;
149838
+ const lastCharCode = str.charCodeAt(maxLen - 1);
149839
+ if (lastCharCode >= 55296 && lastCharCode <= 56319) {
149840
+ return str.slice(0, maxLen - 1);
149841
+ }
149842
+ return str.slice(0, maxLen);
149843
+ }
149844
+ function isEditTool(name2) {
149845
+ return name2 === "edit" || name2 === "write";
149846
+ }
149847
+ function applyEditMarkerToInput(input) {
149848
+ for (const key of Object.keys(input)) {
149849
+ if (PATH_KEYS.has(key))
149850
+ continue;
149851
+ const value = input[key];
149852
+ if (typeof value !== "string" || !DIFF_KEYS.has(key))
149853
+ continue;
149854
+ if (value.endsWith(TRUNCATION_SENTINEL))
149855
+ continue;
149856
+ input[key] = value.length > EDIT_REGION_HINT_LEN ? `${safeSlice(value, EDIT_REGION_HINT_LEN)}${TRUNCATION_SENTINEL}` : value;
149857
+ }
149858
+ }
149859
+ var TRUNCATION_SENTINEL = "...[truncated]", EDIT_REGION_HINT_LEN = 40, PATH_KEYS, DIFF_KEYS;
149860
+ var init_edit_marker = __esm(() => {
149861
+ PATH_KEYS = new Set(["filePath", "file_path", "path"]);
149862
+ DIFF_KEYS = new Set(["oldString", "newString", "content", "old_string", "new_string"]);
149863
+ });
149864
+
149727
149865
  // src/hooks/magic-context/tool-drop-target.ts
149728
149866
  function isToolCallId(value) {
149729
149867
  return typeof value === "string" && value.length > 0;
@@ -149790,7 +149928,41 @@ function estimateInputSize(input) {
149790
149928
  return 0;
149791
149929
  }
149792
149930
  }
149793
- function safeSlice(str, maxLen) {
149931
+ function editMarkerToolPart(part, tagId) {
149932
+ if (!isRecord(part))
149933
+ return;
149934
+ const sentinel = `[dropped §${tagId}§]`;
149935
+ if (part.type === "tool" && isRecord(part.state)) {
149936
+ part.state.output = sentinel;
149937
+ if (isRecord(part.state.input))
149938
+ applyEditMarkerToInput(part.state.input);
149939
+ return;
149940
+ }
149941
+ if (part.type === "tool_result") {
149942
+ part.content = sentinel;
149943
+ return;
149944
+ }
149945
+ if (part.type === "tool-invocation" && isRecord(part.args)) {
149946
+ applyEditMarkerToInput(part.args);
149947
+ return;
149948
+ }
149949
+ if (part.type === "tool_use" && isRecord(part.input)) {
149950
+ applyEditMarkerToInput(part.input);
149951
+ }
149952
+ }
149953
+ function readToolPartInput(part) {
149954
+ if (!isRecord(part))
149955
+ return null;
149956
+ if (part.type === "tool" && isRecord(part.state) && isRecord(part.state.input)) {
149957
+ return part.state.input;
149958
+ }
149959
+ if (part.type === "tool-invocation" && isRecord(part.args))
149960
+ return part.args;
149961
+ if (part.type === "tool_use" && isRecord(part.input))
149962
+ return part.input;
149963
+ return null;
149964
+ }
149965
+ function safeSlice2(str, maxLen) {
149794
149966
  if (str.length <= maxLen)
149795
149967
  return str;
149796
149968
  const lastCharCode = str.charCodeAt(maxLen - 1);
@@ -149803,9 +149975,9 @@ function truncateInputValues(input) {
149803
149975
  for (const key of Object.keys(input)) {
149804
149976
  const value = input[key];
149805
149977
  if (typeof value === "string") {
149806
- if (value.endsWith(TRUNCATION_SENTINEL) || value === "[object]" || /^\[\d+ items\]$/.test(value))
149978
+ if (value.endsWith(TRUNCATION_SENTINEL2) || value === "[object]" || /^\[\d+ items\]$/.test(value))
149807
149979
  continue;
149808
- input[key] = value.length > 5 ? `${safeSlice(value, 5)}${TRUNCATION_SENTINEL}` : value;
149980
+ input[key] = value.length > 5 ? `${safeSlice2(value, 5)}${TRUNCATION_SENTINEL2}` : value;
149809
149981
  } else if (Array.isArray(value)) {
149810
149982
  input[key] = `[${value.length} items]`;
149811
149983
  } else if (value !== null && typeof value === "object") {
@@ -149909,6 +150081,18 @@ function createToolDropTarget(compositeKey, thinkingParts, index, batch, tagId)
149909
150081
  clearThinkingParts(thinkingParts);
149910
150082
  return "truncated";
149911
150083
  };
150084
+ const editMarker = () => {
150085
+ const entry = index.get(compositeKey);
150086
+ if (!entry || entry.occurrences.length === 0)
150087
+ return "absent";
150088
+ if (!entry.hasResult)
150089
+ return "incomplete";
150090
+ for (const occurrence of entry.occurrences) {
150091
+ editMarkerToolPart(occurrence.part, tagId);
150092
+ }
150093
+ clearThinkingParts(thinkingParts);
150094
+ return "truncated";
150095
+ };
149912
150096
  return {
149913
150097
  setContent: (content) => {
149914
150098
  if (isDropContent(content)) {
@@ -149932,14 +150116,34 @@ function createToolDropTarget(compositeKey, thinkingParts, index, batch, tagId)
149932
150116
  },
149933
150117
  drop,
149934
150118
  truncate,
150119
+ editMarker,
149935
150120
  canDrop: () => {
149936
150121
  const entry = index.get(compositeKey);
149937
150122
  return !!entry && entry.occurrences.length > 0 && entry.hasResult;
150123
+ },
150124
+ readInput: () => {
150125
+ const entry = index.get(compositeKey);
150126
+ if (!entry)
150127
+ return null;
150128
+ for (const occurrence of entry.occurrences) {
150129
+ if (occurrence.kind !== "invocation")
150130
+ continue;
150131
+ const input = readToolPartInput(occurrence.part);
150132
+ if (input)
150133
+ return input;
150134
+ }
150135
+ for (const occurrence of entry.occurrences) {
150136
+ const input = readToolPartInput(occurrence.part);
150137
+ if (input)
150138
+ return input;
150139
+ }
150140
+ return null;
149938
150141
  }
149939
150142
  };
149940
150143
  }
149941
- var DROP_PREFIX = "[dropped", IGNORE_PART_TYPES, TRUNCATION_SENTINEL = "...[truncated]";
150144
+ var DROP_PREFIX = "[dropped", IGNORE_PART_TYPES, TRUNCATION_SENTINEL2 = "...[truncated]";
149942
150145
  var init_tool_drop_target = __esm(() => {
150146
+ init_edit_marker();
149943
150147
  init_tag_content_primitives();
149944
150148
  IGNORE_PART_TYPES = new Set([
149945
150149
  "thinking",
@@ -155736,10 +155940,18 @@ function getOldestActiveUnprotectedToolTags(db, sessionId, protectedTags = 0, li
155736
155940
  WHERE session_id = ? AND status = 'active'
155737
155941
  ORDER BY tag_number DESC LIMIT 1 OFFSET ?
155738
155942
  )` : "";
155739
- const params = protectedTags > 0 ? [sessionId, sessionId, protectedTags - 1, boundedLimit] : [sessionId, boundedLimit];
155943
+ const excludeStateTools = RECLAIM_HINT_EXCLUDED_LIST ? `AND (tool_name IS NULL OR tool_name NOT IN (${RECLAIM_HINT_EXCLUDED_LIST}))` : "";
155944
+ const valueFloor = `AND (
155945
+ (token_count IS NULL AND input_token_count IS NULL)
155946
+ OR (COALESCE(token_count, 0) + COALESCE(input_token_count, 0)) >= ?
155947
+ )`;
155948
+ const params = protectedTags > 0 ? [sessionId, RECLAIM_HINT_MIN_TOKENS, sessionId, protectedTags - 1, boundedLimit] : [sessionId, RECLAIM_HINT_MIN_TOKENS, boundedLimit];
155740
155949
  const rows = db.prepare(`SELECT tag_number, tool_name
155741
155950
  FROM tags
155742
- WHERE session_id = ? AND status = 'active' AND type = 'tool' ${whereProtected}
155951
+ WHERE session_id = ? AND status = 'active' AND type = 'tool'
155952
+ ${excludeStateTools}
155953
+ ${valueFloor}
155954
+ ${whereProtected}
155743
155955
  ORDER BY tag_number ASC, id ASC
155744
155956
  LIMIT ?`).all(...params);
155745
155957
  return rows.filter((row) => typeof row.tag_number === "number").map((row) => ({
@@ -155873,7 +156085,7 @@ function toTagEntry(row) {
155873
156085
  messageId: row.message_id,
155874
156086
  type,
155875
156087
  status,
155876
- dropMode: row.drop_mode === "truncated" ? "truncated" : "full",
156088
+ dropMode: row.drop_mode === "truncated" ? "truncated" : row.drop_mode === "edit_marker" ? "edit_marker" : "full",
155877
156089
  toolName: row.tool_name ?? null,
155878
156090
  inputByteSize: row.input_byte_size ?? 0,
155879
156091
  byteSize: row.byte_size,
@@ -156129,7 +156341,7 @@ function deleteToolTagsByOwner(db, sessionId, ownerMsgId) {
156129
156341
  const result = getDeleteToolTagsByOwnerStatement(db).run(sessionId, ownerMsgId);
156130
156342
  return result.changes ?? 0;
156131
156343
  }
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;
156344
+ 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
156345
  var init_storage_tags = __esm(() => {
156134
156346
  insertTagStatements = new WeakMap;
156135
156347
  updateTagStatusStatements = new WeakMap;
@@ -156142,6 +156354,8 @@ var init_storage_tags = __esm(() => {
156142
156354
  updateTagByteSizeStatements = new WeakMap;
156143
156355
  updateTagInputByteSizeStatements = new WeakMap;
156144
156356
  CONTENT_ID_SUFFIX = /:(?:p|file)\d+$/;
156357
+ RECLAIM_HINT_EXCLUDED_TOOLS = ["todowrite"];
156358
+ RECLAIM_HINT_EXCLUDED_LIST = RECLAIM_HINT_EXCLUDED_TOOLS.map((name2) => `'${name2.replace(/'/g, "''")}'`).join(", ");
156145
156359
  updateTagTokenCountStatements = new WeakMap;
156146
156360
  updateTagInputTokenCountStatements = new WeakMap;
156147
156361
  getOwnerScopedToolTagNumbersStatements = new WeakMap;
@@ -165855,7 +166069,7 @@ __export(exports_task_config, {
165855
166069
  dreamTaskScheduled: () => dreamTaskScheduled,
165856
166070
  buildDreamTaskRuntimeConfigs: () => buildDreamTaskRuntimeConfigs
165857
166071
  });
165858
- function buildDreamTaskRuntimeConfigs(dreamer) {
166072
+ function buildDreamTaskRuntimeConfigs(dreamer, language) {
165859
166073
  const tasks = dreamer.tasks ?? {};
165860
166074
  return CANONICAL_DREAM_TASKS.map((task) => {
165861
166075
  const t = tasks[task] ?? {
@@ -165871,6 +166085,7 @@ function buildDreamTaskRuntimeConfigs(dreamer) {
165871
166085
  model,
165872
166086
  fallbackModels,
165873
166087
  thinkingLevel,
166088
+ language,
165874
166089
  timeoutMinutes: t.timeout_minutes ?? 20,
165875
166090
  promotionThreshold: t.promotion_threshold
165876
166091
  };
@@ -177223,8 +177438,8 @@ function buildHistorianFailureNotice(failureCount, lastError) {
177223
177438
  ].join(`
177224
177439
  `);
177225
177440
  }
177226
- function buildHistorianRepairPrompt(originalPrompt, previousOutput, validationError) {
177227
- return [
177441
+ function buildHistorianRepairPrompt(originalPrompt, previousOutput, validationError, language) {
177442
+ const prompt = [
177228
177443
  originalPrompt,
177229
177444
  "",
177230
177445
  "Your previous XML response was invalid and cannot be persisted.",
@@ -177237,6 +177452,7 @@ function buildHistorianRepairPrompt(originalPrompt, previousOutput, validationEr
177237
177452
  previousOutput
177238
177453
  ].join(`
177239
177454
  `);
177455
+ return withContentLanguageDirective(prompt, language, { preserveUserQuotes: true });
177240
177456
  }
177241
177457
  function validateStoredCompartments(compartments) {
177242
177458
  if (compartments.length === 0) {
@@ -177313,6 +177529,7 @@ function getReducedRecompTokenBudget(currentBudget) {
177313
177529
  }
177314
177530
  var MIN_RECOMP_CHUNK_TOKEN_BUDGET = 20, HISTORIAN_PERSISTENT_FAILURE_THRESHOLD = 3;
177315
177531
  var init_compartment_runner_validation = __esm(async () => {
177532
+ init_language_directive();
177316
177533
  init_compartment_parser();
177317
177534
  await init_compartment_runner_mapping();
177318
177535
  });
@@ -177350,7 +177567,7 @@ async function runValidatedHistorianPass(args) {
177350
177567
  return finalResult;
177351
177568
  }
177352
177569
  await args.callbacks?.onRepairRetry?.(firstValidation.error ?? "invalid compartment output");
177353
- const repairPrompt = buildHistorianRepairPrompt(args.prompt, firstRun.result, firstValidation.error ?? "invalid compartment output");
177570
+ const repairPrompt = buildHistorianRepairPrompt(args.prompt, firstRun.result, firstValidation.error ?? "invalid compartment output", args.language);
177354
177571
  const repairRun = await runHistorianPrompt({
177355
177572
  ...args,
177356
177573
  prompt: repairPrompt,
@@ -182735,7 +182952,8 @@ ${chunkText}`,
182735
182952
  timeoutMs: historianTimeoutMs,
182736
182953
  fallbackModelId: deps.fallbackModelId,
182737
182954
  fallbackModels: deps.fallbackModels,
182738
- twoPass: deps.historianTwoPass
182955
+ twoPass: deps.historianTwoPass,
182956
+ language: deps.language
182739
182957
  });
182740
182958
  if (!validatedPass.ok) {
182741
182959
  sessionLog(sessionId, `historian failure: source=validation reason="${validatedPass.error}" chunkRange=${chunk.startIndex}-${chunk.endIndex} fallbackModel=${deps.fallbackModelId ?? "<none>"} twoPass=${deps.historianTwoPass ? "true" : "false"}`);
@@ -183211,6 +183429,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
183211
183429
  twoPass: deps.historianTwoPass,
183212
183430
  subagentKind: "recomp",
183213
183431
  agentId: HISTORIAN_RECOMP_AGENT,
183432
+ language: deps.language,
183214
183433
  callbacks: {
183215
183434
  onRepairRetry: async (error51) => {
183216
183435
  emitProgress(`Repair retry (pass ${passCount + 1})…`);
@@ -183653,6 +183872,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
183653
183872
  twoPass: deps.historianTwoPass,
183654
183873
  subagentKind: "recomp",
183655
183874
  agentId: HISTORIAN_RECOMP_AGENT,
183875
+ language: deps.language,
183656
183876
  callbacks: {
183657
183877
  onRepairRetry: async (error51) => {
183658
183878
  await sendIgnoredMessage(client, sessionId, `## Magic Recomp — Partial
@@ -186013,7 +186233,7 @@ async function runMemoryMigration(deps) {
186013
186233
  query: { directory },
186014
186234
  body: {
186015
186235
  agent: HISTORIAN_AGENT,
186016
- system: MIGRATION_SYSTEM_PROMPT,
186236
+ system: withMigrationLanguageDirective(MIGRATION_SYSTEM_PROMPT, deps.language),
186017
186237
  ...modelOverride ? { model: modelOverride } : {},
186018
186238
  parts: [{ type: "text", text: prompt, synthetic: true }]
186019
186239
  }
@@ -186092,6 +186312,7 @@ async function runMemoryMigration(deps) {
186092
186312
  }
186093
186313
  var V2_CATEGORIES, MIGRATED_BLOCK_RE, USER_OBS_BLOCK_RE, CATEGORY_BLOCK_RE = (cat) => new RegExp(`<${cat}>([\\s\\S]*?)</${cat}>`), MIGRATION_SYSTEM_PROMPT;
186094
186314
  var init_memory_migration = __esm(async () => {
186315
+ init_language_directive();
186095
186316
  init_shared();
186096
186317
  init_assistant_message_extractor();
186097
186318
  init_logger();
@@ -186208,6 +186429,7 @@ function buildRecompDeps(ctx, sessionId) {
186208
186429
  memoryEnabled: ctx.memoryEnabled,
186209
186430
  autoPromote: ctx.autoPromote,
186210
186431
  fallbackModels: ctx.fallbackModels,
186432
+ language: ctx.language,
186211
186433
  fallbackModelId: ctx.fallbackModelId ?? resolveLiveModelKey(ctx.liveSessionState, sessionId),
186212
186434
  historianTwoPass: ctx.historianTwoPass,
186213
186435
  ensureProjectRegistered: ctx.ensureProjectRegistered,
@@ -186339,7 +186561,8 @@ async function runUpgradeMemoryMigration(ctx, sessionId, migrationDirectory) {
186339
186561
  primaryModelId: ctx.fallbackModelId ?? resolveLiveModelKey(ctx.liveSessionState, sessionId),
186340
186562
  fallbackModels: ctx.fallbackModels,
186341
186563
  timeoutMs: ctx.historianTimeoutMs,
186342
- userMemoriesEnabled: ctx.userMemoriesEnabled
186564
+ userMemoriesEnabled: ctx.userMemoriesEnabled,
186565
+ language: ctx.language
186343
186566
  });
186344
186567
  return outcome.summary;
186345
186568
  } catch (error51) {
@@ -186415,15 +186638,12 @@ function shouldShowAnnouncement() {
186415
186638
  }
186416
186639
  return ordering > 0;
186417
186640
  }
186418
- var ANNOUNCEMENT_VERSION = "0.27.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
186641
+ var ANNOUNCEMENT_VERSION = "0.29.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
186419
186642
  var init_announcement = __esm(() => {
186420
186643
  init_data_path();
186421
186644
  ANNOUNCEMENT_FEATURES = [
186422
- "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.",
186423
- "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.",
186424
- "New Primers: durable answers to the questions that keep coming up about your project, kept current by the dreamer investigating the actual code.",
186425
- "Embedding storage no longer wipes your vectors when you change model or endpoint. Different models now coexist, so switching providers keeps your existing vectors.",
186426
- "Config moved to a shared CortexKit location (~/.config/cortexkit/ and <project>/.cortexkit/). This happens automatically on first run; your old file is preserved."
186645
+ `New 'smart_drops' option (opt-in, off by default): on long, edit-heavy sessions it reclaims more context by dropping tool output a later call made obsolete (superseded edits, old todowrite/ctx_reduce, spent status calls) instead of only the oldest. When off, the prompt is byte-identical to before. Enable with "smart_drops": true.`,
186646
+ "When memory is disabled, the ctx_memory tool and its guidance are no longer shown (they had nothing to write to). ctx_search stays, searching conversation history and git commits."
186427
186647
  ];
186428
186648
  });
186429
186649
 
@@ -186717,6 +186937,9 @@ function buildHiddenAgentConfig(prompt, allowedTools, maxSteps, overrides, agent
186717
186937
  };
186718
186938
  }
186719
186939
 
186940
+ // src/index.ts
186941
+ init_language_directive();
186942
+
186720
186943
  // src/config/index.ts
186721
186944
  init_jsonc_parser();
186722
186945
  import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
@@ -187461,6 +187684,10 @@ function stripUnsafeProjectConfigFields(projectRaw) {
187461
187684
  delete projectRaw.auto_update;
187462
187685
  warnings.push("Ignoring auto_update from project config (security: this setting only honors user-level config).");
187463
187686
  }
187687
+ if ("language" in projectRaw) {
187688
+ delete projectRaw.language;
187689
+ warnings.push("Ignoring language from project config (security: output language is a user-level setting).");
187690
+ }
187464
187691
  if ("sqlite" in projectRaw) {
187465
187692
  delete projectRaw.sqlite;
187466
187693
  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).");
@@ -188024,6 +188251,7 @@ var MAINTAIN_DOCS_SYSTEM_PROMPT = `You are a documentation maintainer for the ma
188024
188251
 
188025
188252
  ## Rules
188026
188253
  - **NEVER touch protected regions.** Any content between \`<!-- mc:protected START ... -->\` and \`<!-- mc:protected END -->\` is hand-authored and cache-critical. Reproduce it BYTE-FOR-BYTE — do not edit, reword, reorder, summarize, trim, or drop a single line, and keep the marker comments. Only a human edits that region.
188254
+ - **Preserve an existing doc's structure, voice, and density.** When a doc already exists, it is the source of truth for shape: keep its headings, ordering, level of detail, and writing style. Make the SMALLEST edits that bring it back in sync with the code. NEVER reshape hand-written prose into a generic template, collapse a dense section into bullet stubs, or drop hard-won detail (specific invariants, edge cases, mechanism descriptions) because it does not fit a standard layout. A doc denser and more specific than a template is BETTER, not worse: leave it that way.
188027
188255
  - **Be prescriptive** ("Use X pattern", not "X pattern is used"). **Current state only** — no temporal language, no history.
188028
188256
  - **Verify before writing** — read the actual files, never guess. All file paths in the docs must point to files that exist.`;
188029
188257
  var REVIEW_USER_MEMORIES_SYSTEM_PROMPT = `You are a user-profile reviewer for the magic-context system. You run during a scheduled dream window to decide which recurring behavioral observations about the human user are real, persistent patterns worth keeping in their global user profile.
@@ -188139,7 +188367,7 @@ Return only XML in this exact shape:
188139
188367
  function buildMaintainDocsPrompt(projectPath, lastDreamAt, existingDocs) {
188140
188368
  const hasAny = existingDocs.architecture || existingDocs.structure;
188141
188369
  const gitSinceClause = lastDreamAt ? `Run \`git log --oneline --since="${new Date(Number(lastDreamAt)).toISOString()}"\` to see what changed since the last dream.` : "No previous dream timestamp — treat this as a full analysis.";
188142
- const modeIntro = hasAny ? `Some docs already exist. Update only the sections affected by recent changes. Do NOT rewrite unchanged sections.` : `No docs exist yet. Create both ARCHITECTURE.md and STRUCTURE.md from scratch using the templates below.`;
188370
+ const modeIntro = hasAny ? `Some docs already exist and are the source of truth for shape. Make SURGICAL \`edit\` changes to only the sections affected by recent code changes; preserve every other section, the existing structure, and the existing density verbatim. Do NOT regenerate a whole file, do NOT reshape prose into a template, and do NOT use the templates below (they are for creation only). If nothing material changed, change nothing.` : `No docs exist yet. Create both ARCHITECTURE.md and STRUCTURE.md from scratch using the templates below as a STARTING shape, then go deeper than the template wherever the code warrants it.`;
188143
188371
  return `## Task: Maintain Codebase Documentation
188144
188372
 
188145
188373
  **Project:** ${projectPath}
@@ -188154,15 +188382,16 @@ ${modeIntro}
188154
188382
  ### Process
188155
188383
 
188156
188384
  1. **Check what changed.** ${gitSinceClause}
188157
- 2. **Read existing docs** (if they exist) to understand current state.
188385
+ 2. **Read existing docs** (if they exist) IN FULL to understand their current structure, depth, and voice; you will preserve all of it except what code changes force you to touch.
188158
188386
  3. **Explore the codebase** to verify and update:
188159
188387
  - Directory structure: \`find . -type d -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' | head -60\`
188160
188388
  - Entry points: \`ls src/index.* src/main.* 2>/dev/null\`
188161
188389
  - Key imports: \`grep -r "^import\\|^export" src/ --include="*.ts" | head -80\`
188162
- 4. **Write or update** using the Write tool. Always write to project root, NOT to .planning/.
188390
+ 4. **Apply the change.** If the doc EXISTS: use \`edit\` for the specific sections that drifted, never rewrite the whole file with \`write\`. If the doc is MISSING: create it with \`write\`. Always at project root, NOT \`.planning/\`.
188163
188391
 
188164
188392
  ### Rules
188165
188393
  - **NEVER touch protected regions**: any content between \`<!-- mc:protected START ... -->\` and \`<!-- mc:protected END -->\` is hand-authored and cache-critical. Reproduce it BYTE-FOR-BYTE in your rewrite — do not edit, reword, reorder, summarize, trim, or drop a single line of it, and keep the marker comments themselves. Only a human edits that region.
188394
+ - **Preserve existing structure and density**: when a doc exists, keep its headings, ordering, level of detail, and voice. Make the smallest edits that re-sync it with the code. NEVER flatten dense hand-written prose into the generic template, collapse a detailed section into bullet stubs, or drop specific invariants/edge-cases/mechanism detail because it does not match a standard layout. Denser and more specific than the template is BETTER.
188166
188395
  - **Be prescriptive**: "Use X pattern" not "X pattern is used"
188167
188396
  - **Always include file paths** in backticks
188168
188397
  - **Write current state only**: no temporal language, no history
@@ -188293,6 +188522,9 @@ function buildDreamTaskPrompt(task, args) {
188293
188522
  // src/index.ts
188294
188523
  init_project_identity();
188295
188524
 
188525
+ // src/features/magic-context/sidekick/agent.ts
188526
+ init_language_directive();
188527
+
188296
188528
  // src/agents/sidekick.ts
188297
188529
  var SIDEKICK_AGENT = "sidekick";
188298
188530
 
@@ -188360,12 +188592,13 @@ async function runSidekick(deps) {
188360
188592
  throw error51;
188361
188593
  }
188362
188594
  const childSessionId = agentSessionId;
188595
+ const systemPrompt = withContentLanguageDirective(deps.config.system_prompt?.trim() || deps.config.prompt?.trim() || SIDEKICK_SYSTEM_PROMPT, deps.language);
188363
188596
  const sidekickRun = await promptSyncWithValidatedOutputRetry(deps.client, {
188364
188597
  path: { id: childSessionId },
188365
188598
  query: { directory: deps.sessionDirectory ?? deps.projectPath },
188366
188599
  body: {
188367
188600
  agent: SIDEKICK_AGENT,
188368
- system: deps.config.system_prompt?.trim() || deps.config.prompt?.trim() || SIDEKICK_SYSTEM_PROMPT,
188601
+ system: systemPrompt,
188369
188602
  parts: [{ type: "text", text: deps.userMessage, synthetic: true }]
188370
188603
  }
188371
188604
  }, {
@@ -189928,6 +190161,7 @@ var DREAMER_DOCS_AGENT = "dreamer-docs";
189928
190161
  var DREAMER_REVIEWER_AGENT = "dreamer-reviewer";
189929
190162
 
189930
190163
  // src/features/magic-context/dreamer/task-executor.ts
190164
+ init_language_directive();
189931
190165
  init_shared();
189932
190166
  init_assistant_message_extractor();
189933
190167
  init_logger();
@@ -189936,6 +190170,7 @@ init_memory();
189936
190170
  init_subagent_token_capture();
189937
190171
 
189938
190172
  // src/features/magic-context/user-memory/review-user-memories.ts
190173
+ init_language_directive();
189939
190174
  init_shared();
189940
190175
  init_assistant_message_extractor();
189941
190176
  init_logger();
@@ -190050,7 +190285,7 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
190050
190285
  query: { directory: args.sessionDirectory },
190051
190286
  body: {
190052
190287
  agent: DREAMER_REVIEWER_AGENT,
190053
- system: REVIEW_USER_MEMORIES_SYSTEM_PROMPT,
190288
+ system: withContentLanguageDirective(REVIEW_USER_MEMORIES_SYSTEM_PROMPT, args.language),
190054
190289
  ...modelBodyField(args.model),
190055
190290
  parts: [{ type: "text", text: prompt, synthetic: true }]
190056
190291
  }
@@ -191136,10 +191371,23 @@ function getAsyncModule() {
191136
191371
  asyncModulePromise ??= newQuickJSAsyncWASMModuleFromVariant(import_quickjs_singlefile_cjs_release_asyncify.default);
191137
191372
  return asyncModulePromise;
191138
191373
  }
191374
+ var sandboxRunChain = Promise.resolve();
191375
+ function withSandboxLock(fn) {
191376
+ const run = sandboxRunChain.then(fn, fn);
191377
+ sandboxRunChain = run.then(() => {
191378
+ return;
191379
+ }, () => {
191380
+ return;
191381
+ });
191382
+ return run;
191383
+ }
191139
191384
  var DEFAULT_TIMEOUT_MS = 2000;
191140
191385
  var DEFAULT_HEAP_LIMIT_BYTES = 8 * 1024 * 1024;
191141
191386
  var DEFAULT_STACK_LIMIT_BYTES = 512 * 1024;
191142
191387
  async function runCompiledSmartNoteCheck(options) {
191388
+ return withSandboxLock(() => runCompiledSmartNoteCheckLocked(options));
191389
+ }
191390
+ async function runCompiledSmartNoteCheckLocked(options) {
191143
191391
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
191144
191392
  const controller = new AbortController;
191145
191393
  const externalAbort = () => controller.abort(options.signal?.reason);
@@ -192634,6 +192882,7 @@ async function promotePrimers(args) {
192634
192882
  }
192635
192883
 
192636
192884
  // src/features/magic-context/dreamer/refresh-primers.ts
192885
+ init_language_directive();
192637
192886
  init_read_session_formatting();
192638
192887
  init_shared();
192639
192888
  init_assistant_message_extractor();
@@ -192872,7 +193121,7 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
192872
193121
  query: { directory: args.sessionDirectory },
192873
193122
  body: {
192874
193123
  agent: DREAMER_PRIMER_INVESTIGATOR_AGENT,
192875
- system: PRIMER_INVESTIGATOR_SYSTEM_PROMPT,
193124
+ system: withContentLanguageDirective(PRIMER_INVESTIGATOR_SYSTEM_PROMPT, args.language),
192876
193125
  ...modelBodyField(args.model),
192877
193126
  parts: [{ type: "text", text: prompt, synthetic: true }]
192878
193127
  }
@@ -193108,6 +193357,7 @@ function insertDreamRun(db, run) {
193108
193357
  init_task_registry();
193109
193358
 
193110
193359
  // src/features/magic-context/dreamer/verify.ts
193360
+ init_language_directive();
193111
193361
  init_shared();
193112
193362
  init_assistant_message_extractor();
193113
193363
  init_logger();
@@ -193335,7 +193585,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
193335
193585
  query: { directory: args.sessionDirectory },
193336
193586
  body: {
193337
193587
  agent: DREAMER_MEMORY_MAPPER_AGENT,
193338
- system: VERIFY_SYSTEM_PROMPT,
193588
+ system: withContentLanguageDirective(VERIFY_SYSTEM_PROMPT, args.language),
193339
193589
  ...modelBodyField(args.model),
193340
193590
  parts: [{ type: "text", text: prompt, synthetic: true }]
193341
193591
  }
@@ -193615,7 +193865,8 @@ function createDreamTaskExecutor(deps) {
193615
193865
  deadline,
193616
193866
  promotionThreshold: config2.promotionThreshold ?? 3,
193617
193867
  model: config2.model,
193618
- fallbackModels: config2.fallbackModels
193868
+ fallbackModels: config2.fallbackModels,
193869
+ language: config2.language ?? deps.language
193619
193870
  });
193620
193871
  recordRun("completed", null);
193621
193872
  log(`[dreamer] review-user-memories: promoted=${result.promoted} merged=${result.merged} dismissed=${result.dismissed}`);
@@ -193651,7 +193902,8 @@ function createDreamTaskExecutor(deps) {
193651
193902
  deadline,
193652
193903
  forceBroad: config2.task === "verify-broad",
193653
193904
  model: config2.model,
193654
- fallbackModels: config2.fallbackModels
193905
+ fallbackModels: config2.fallbackModels,
193906
+ language: config2.language ?? deps.language
193655
193907
  });
193656
193908
  recordRun("completed", null, {
193657
193909
  memoryChanges: computeMemoryDelta(memoryBefore)
@@ -193703,6 +193955,7 @@ function createDreamTaskExecutor(deps) {
193703
193955
  deadline,
193704
193956
  model: config2.model,
193705
193957
  fallbackModels: config2.fallbackModels,
193958
+ language: config2.language ?? deps.language,
193706
193959
  rawProviderFactory: deps.primerRawProviderFactory
193707
193960
  });
193708
193961
  recordRun("completed", null);
@@ -193820,6 +194073,7 @@ function retrospectiveEventsForSessions(db, sessionIds) {
193820
194073
  try {
193821
194074
  for (const event of getCompartmentEvents(db, sessionId)) {
193822
194075
  if (event.kind !== "causal_incident" && event.kind !== "trajectory_correction") {
194076
+ log(`[dreamer] dropping event: unknown kind="${event.kind}"`);
193823
194077
  continue;
193824
194078
  }
193825
194079
  events.push({
@@ -193944,7 +194198,9 @@ async function runRetrospectiveTask(config2, ctx, helpers) {
193944
194198
  const frictionWindow = renderFrictionWindow(messages, flagged.map((message) => message.ordinal));
193945
194199
  const eventSessionIds = new Set(messages.map((message) => message.sessionId));
193946
194200
  const events = retrospectiveEventsForSessions(db, eventSessionIds);
193947
- const deepenRun = await runChildTurn(RETROSPECTIVE_SYSTEM_PROMPT, buildRetrospectivePrompt({ projectPath: projectIdentity, frictionWindow, events }));
194201
+ const deepenRun = await runChildTurn(withContentLanguageDirective(RETROSPECTIVE_SYSTEM_PROMPT, config2.language ?? deps.language, {
194202
+ retrospective: true
194203
+ }), buildRetrospectivePrompt({ projectPath: projectIdentity, frictionWindow, events }));
193948
194204
  if (leaseLost)
193949
194205
  throw new Error("Dream lease lost during retrospective");
193950
194206
  const sourceSessionId = flagged[0]?.sessionId ?? userMessages[0]?.sessionId ?? "retrospective";
@@ -194030,7 +194286,7 @@ async function runAgenticTask(config2, ctx, helpers) {
194030
194286
  query: { directory: docsDir },
194031
194287
  body: {
194032
194288
  agent: task === "maintain-docs" ? DREAMER_DOCS_AGENT : DREAMER_AGENT,
194033
- system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : CURATE_SYSTEM_PROMPT,
194289
+ system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : withContentLanguageDirective(CURATE_SYSTEM_PROMPT, config2.language ?? deps.language),
194034
194290
  ...modelBodyField(config2.model),
194035
194291
  parts: [{ type: "text", text: taskPrompt, synthetic: true }]
194036
194292
  }
@@ -194852,7 +195108,7 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
194852
195108
  }
194853
195109
  try {
194854
195110
  await runCompiledSmartNoteSweep(reg, db);
194855
- const runtimeConfigs = buildDreamTaskRuntimeConfigs(dreamerConfig);
195111
+ const runtimeConfigs = buildDreamTaskRuntimeConfigs(dreamerConfig, reg.language);
194856
195112
  const executor = createDreamTaskExecutor({
194857
195113
  client: reg.client,
194858
195114
  sessionDirectory: reg.directory,
@@ -194860,7 +195116,8 @@ async function sweepProject(reg, origin, db, gitCommitEnabled) {
194860
195116
  retrospectiveRawProvider: reg.retrospectiveRawProvider ?? ((db2) => new OpenCodeRetrospectiveRawProvider({ contextDb: db2, openOpenCodeDb })),
194861
195117
  primerRawProviderFactory: reg.primerRawProviderFactory,
194862
195118
  userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreamerConfig),
194863
- ensureProjectRegistered: reg.ensureRegistered
195119
+ ensureProjectRegistered: reg.ensureRegistered,
195120
+ language: reg.language
194864
195121
  });
194865
195122
  const ran = await runDueTasksForProject({
194866
195123
  db,
@@ -196224,7 +196481,8 @@ Provide a prompt to augment with project memory context.`, {});
196224
196481
  projectPath: deps.sidekick.projectPath,
196225
196482
  sessionDirectory: deps.sidekick.sessionDirectory,
196226
196483
  userMessage: prompt,
196227
- config: deps.sidekick.config
196484
+ config: deps.sidekick.config,
196485
+ language: deps.sidekick.language
196228
196486
  });
196229
196487
  let augmentedPrompt;
196230
196488
  if (sidekickResult) {
@@ -196831,6 +197089,7 @@ function channel1RefireTokens(workingWindowTokens) {
196831
197089
  var S_GENTLE = 0.2;
196832
197090
  var S_FIRM = 0.4;
196833
197091
  var S_URGENT = 0.65;
197092
+ var CHANNEL1_PRESSURE_FLOOR = 0.8;
196834
197093
  var LEVEL_RANK = { gentle: 1, firm: 2, urgent: 3 };
196835
197094
  var DROP_SENTINELS = ["[dropped", "[truncated"];
196836
197095
  function isDroppedToolOutput(output) {
@@ -196895,7 +197154,8 @@ function computeTailTokenEstimate(messages) {
196895
197154
  };
196896
197155
  }
196897
197156
  function decideChannel1(input) {
196898
- const { undroppedTokens, pressure, workingWindowTokens, hasRecentReduce } = input;
197157
+ const { undroppedTokens, workingWindowTokens, hasRecentReduce } = input;
197158
+ const pressure = Math.min(1, Math.max(0, input.pressure));
196899
197159
  const resetCycle = hasRecentReduce || undroppedTokens < input.lastNudgeUndropped;
196900
197160
  const lastNudge = resetCycle ? 0 : input.lastNudgeUndropped;
196901
197161
  const lastLevel = resetCycle ? "" : input.lastNudgeLevel;
@@ -196910,8 +197170,10 @@ function decideChannel1(input) {
196910
197170
  return quiet();
196911
197171
  if (undroppedTokens < CHANNEL1_FLOOR_TOKENS)
196912
197172
  return quiet();
196913
- const budget = workingWindowTokens > 0 ? workingWindowTokens : undroppedTokens || 1;
196914
- const severity = undroppedTokens / budget * pressure;
197173
+ if (pressure < CHANNEL1_PRESSURE_FLOOR)
197174
+ return quiet();
197175
+ const denom = Math.max(input.estimatedInputTokens, 1);
197176
+ const severity = Math.min(1, undroppedTokens / denom);
196915
197177
  if (severity < S_GENTLE)
196916
197178
  return quiet();
196917
197179
  let level;
@@ -198580,7 +198842,7 @@ var RECENT_TOOL_SKELETON_WINDOW = 20;
198580
198842
  function buildReplacementContent(tagId) {
198581
198843
  return `[dropped §${tagId}§]`;
198582
198844
  }
198583
- function applyPendingOperations(sessionId, db, targets, protectedTags = 0, preloadedTags, preloadedPendingOps, syntheticPendingOps = []) {
198845
+ function applyPendingOperations(sessionId, db, targets, protectedTags = 0, preloadedTags, preloadedPendingOps, syntheticPendingOps = [], editMarkerTagIds = new Set) {
198584
198846
  let didMutateMessage = false;
198585
198847
  db.transaction(() => {
198586
198848
  const tags = preloadedTags ?? getTagsBySession(db, sessionId);
@@ -198611,7 +198873,15 @@ function applyPendingOperations(sessionId, db, targets, protectedTags = 0, prelo
198611
198873
  }
198612
198874
  let shouldPersistDrop = false;
198613
198875
  if (isToolTag) {
198614
- if (skeletonWindow.has(pendingOp.tagId)) {
198876
+ if (editMarkerTagIds.has(pendingOp.tagId)) {
198877
+ const markResult = target?.editMarker?.() ?? "absent";
198878
+ if (markResult === "incomplete" || markResult === "absent") {
198879
+ continue;
198880
+ }
198881
+ didMutateMessage = true;
198882
+ updateTagDropMode(db, sessionId, pendingOp.tagId, "edit_marker");
198883
+ shouldPersistDrop = true;
198884
+ } else if (skeletonWindow.has(pendingOp.tagId)) {
198615
198885
  const truncResult = target?.truncate?.() ?? "absent";
198616
198886
  if (truncResult === "incomplete" || synthetic && truncResult !== "truncated") {
198617
198887
  continue;
@@ -198656,7 +198926,12 @@ function applyFlushedStatuses(sessionId, db, targets, preloadedTags) {
198656
198926
  if (tag.status === "dropped") {
198657
198927
  const target = targets.get(tag.tagNumber);
198658
198928
  if (tag.type === "tool") {
198659
- if (tag.dropMode === "truncated") {
198929
+ if (tag.dropMode === "edit_marker") {
198930
+ const markResult = target?.editMarker?.() ?? "absent";
198931
+ if (markResult === "truncated") {
198932
+ didMutateMessage = true;
198933
+ }
198934
+ } else if (tag.dropMode === "truncated") {
198660
198935
  const truncResult = target?.truncate?.() ?? "absent";
198661
198936
  if (truncResult === "truncated") {
198662
198937
  didMutateMessage = true;
@@ -198711,6 +198986,7 @@ function stripStructuralNoise(messages) {
198711
198986
  // src/hooks/magic-context/tag-messages.ts
198712
198987
  init_storage_tags();
198713
198988
  await init_storage();
198989
+ init_commit_detection();
198714
198990
  // src/hooks/magic-context/drop-stale-reduce-calls.ts
198715
198991
  var STALE_TOOL_NAMES = new Set(["ctx_reduce"]);
198716
198992
  function isReduceToolPart(part) {
@@ -199031,7 +199307,6 @@ function tagMessages(sessionId, messages, tagger, db, options = {}) {
199031
199307
  let lastReduceMessageIndex = -1;
199032
199308
  const RECENT_REDUCE_LOOKBACK = 10;
199033
199309
  const COMMIT_LOOKBACK = 5;
199034
- const COMMIT_HASH_PATTERN2 = /\b[0-9a-f]{7,12}\b/;
199035
199310
  let commitDetected = false;
199036
199311
  let accDerive = 0;
199037
199312
  let accGetToolTag = 0;
@@ -199222,7 +199497,7 @@ function tagMessages(sessionId, messages, tagger, db, options = {}) {
199222
199497
  for (const part of message.parts) {
199223
199498
  if (isTextPart(part)) {
199224
199499
  const text = part.text;
199225
- if (COMMIT_HASH_PATTERN2.test(text) && /\b(commit|committed|cherry-pick|merge|rebas)/i.test(text)) {
199500
+ if (textMentionsRecentCommit(text)) {
199226
199501
  commitDetected = true;
199227
199502
  break;
199228
199503
  }
@@ -200717,6 +200992,99 @@ function isVisibleNoteReadPart(part) {
200717
200992
  return false;
200718
200993
  }
200719
200994
 
200995
+ // src/hooks/magic-context/supersession-reclaim.ts
200996
+ init_edit_marker();
200997
+ await init_storage();
200998
+ var TODOWRITE_KEEP = 1;
200999
+ var CTX_REDUCE_KEEP = 5;
201000
+ var ZERO_VALUE_META_TOOLS = new Set(["bash_status", "bash_kill"]);
201001
+ var CTX_NOTE_ZERO_VALUE_ACTIONS = new Set(["read", "dismiss"]);
201002
+ function buildSupersessionReclaimOps(input) {
201003
+ const realPendingTagIds = new Set((input.pendingOps ?? []).map((op) => op.tagId));
201004
+ const tags = getActiveTagsBySession(input.db, input.sessionId);
201005
+ const toolTags = tags.filter((tag) => tag.type === "tool" && tag.status === "active").sort((left, right) => right.tagNumber - left.tagNumber);
201006
+ const dropTagIds = [];
201007
+ let todowriteSeen = 0;
201008
+ let ctxReduceSeen = 0;
201009
+ for (const tag of toolTags) {
201010
+ const name2 = tag.toolName;
201011
+ if (!name2)
201012
+ continue;
201013
+ let isTarget = false;
201014
+ if (name2 === "todowrite") {
201015
+ todowriteSeen += 1;
201016
+ isTarget = todowriteSeen > TODOWRITE_KEEP;
201017
+ } else if (name2 === "ctx_reduce") {
201018
+ ctxReduceSeen += 1;
201019
+ isTarget = ctxReduceSeen > CTX_REDUCE_KEEP;
201020
+ } else if (ZERO_VALUE_META_TOOLS.has(name2)) {
201021
+ isTarget = true;
201022
+ } else if (name2 === "ctx_note") {
201023
+ const action = input.targets.get(tag.tagNumber)?.readInput?.()?.action;
201024
+ isTarget = typeof action === "string" && CTX_NOTE_ZERO_VALUE_ACTIONS.has(action);
201025
+ }
201026
+ if (isTarget)
201027
+ dropTagIds.push(tag.tagNumber);
201028
+ }
201029
+ const synthetic = [];
201030
+ for (const tagId of dropTagIds) {
201031
+ if (realPendingTagIds.has(tagId))
201032
+ continue;
201033
+ if (input.targets.get(tagId)?.canDrop?.() !== true)
201034
+ continue;
201035
+ synthetic.push({
201036
+ id: 0,
201037
+ sessionId: input.sessionId,
201038
+ tagId,
201039
+ operation: "drop",
201040
+ queuedAt: 0
201041
+ });
201042
+ }
201043
+ return synthetic;
201044
+ }
201045
+ function buildEditSupersessionReclaim(input) {
201046
+ const realPendingTagIds = new Set((input.pendingOps ?? []).map((op) => op.tagId));
201047
+ const tags = getActiveTagsBySession(input.db, input.sessionId);
201048
+ const editTags = tags.filter((tag) => tag.type === "tool" && tag.status === "active" && isEditTool(tag.toolName)).sort((left, right) => right.tagNumber - left.tagNumber);
201049
+ const seenFile = new Set;
201050
+ const ops = [];
201051
+ const editMarkerTagIds = new Set;
201052
+ for (const tag of editTags) {
201053
+ const filePath = readFilePath(input.targets.get(tag.tagNumber));
201054
+ if (!filePath)
201055
+ continue;
201056
+ if (!seenFile.has(filePath)) {
201057
+ seenFile.add(filePath);
201058
+ continue;
201059
+ }
201060
+ if (realPendingTagIds.has(tag.tagNumber))
201061
+ continue;
201062
+ if (input.targets.get(tag.tagNumber)?.canDrop?.() !== true)
201063
+ continue;
201064
+ editMarkerTagIds.add(tag.tagNumber);
201065
+ ops.push({
201066
+ id: 0,
201067
+ sessionId: input.sessionId,
201068
+ tagId: tag.tagNumber,
201069
+ operation: "drop",
201070
+ queuedAt: 0
201071
+ });
201072
+ }
201073
+ return { ops, editMarkerTagIds };
201074
+ }
201075
+ var FILE_PATH_KEYS = ["filePath", "file_path", "path"];
201076
+ function readFilePath(target) {
201077
+ const input = target?.readInput?.();
201078
+ if (!input)
201079
+ return null;
201080
+ for (const key of FILE_PATH_KEYS) {
201081
+ const value = input[key];
201082
+ if (typeof value === "string" && value.length > 0)
201083
+ return value;
201084
+ }
201085
+ return null;
201086
+ }
201087
+
200720
201088
  // src/hooks/magic-context/todo-view.ts
200721
201089
  import { createHash as createHash12 } from "node:crypto";
200722
201090
  var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
@@ -200987,9 +201355,38 @@ async function runPostTransformPhase(args) {
200987
201355
  watermark: args.sessionMeta.toolReclaimWatermark ?? 0,
200988
201356
  pendingOps
200989
201357
  });
201358
+ const editMarkerTagIds = new Set;
201359
+ if (args.smartDrops) {
201360
+ const selectedIds = new Set(syntheticPendingOps.map((op) => op.tagId));
201361
+ const supersessionOps = buildSupersessionReclaimOps({
201362
+ db: args.db,
201363
+ sessionId: args.sessionId,
201364
+ targets: args.targets,
201365
+ pendingOps
201366
+ });
201367
+ for (const op of supersessionOps) {
201368
+ if (!selectedIds.has(op.tagId)) {
201369
+ syntheticPendingOps.push(op);
201370
+ selectedIds.add(op.tagId);
201371
+ }
201372
+ }
201373
+ const editReclaim = buildEditSupersessionReclaim({
201374
+ db: args.db,
201375
+ sessionId: args.sessionId,
201376
+ targets: args.targets,
201377
+ pendingOps
201378
+ });
201379
+ for (const op of editReclaim.ops) {
201380
+ if (!selectedIds.has(op.tagId)) {
201381
+ syntheticPendingOps.push(op);
201382
+ selectedIds.add(op.tagId);
201383
+ editMarkerTagIds.add(op.tagId);
201384
+ }
201385
+ }
201386
+ }
200990
201387
  autoReclaimTargetCount = syntheticPendingOps.length;
200991
201388
  if (syntheticPendingOps.length > 0) {
200992
- autoReclaimDidMutate = applyPendingOperations(args.sessionId, args.db, args.targets, args.protectedTags, undefined, [], syntheticPendingOps);
201389
+ autoReclaimDidMutate = applyPendingOperations(args.sessionId, args.db, args.targets, args.protectedTags, undefined, [], syntheticPendingOps, editMarkerTagIds);
200993
201390
  if (autoReclaimDidMutate) {
200994
201391
  droppedCount += syntheticPendingOps.length;
200995
201392
  autoReclaimDidMutateThisPass = true;
@@ -201931,6 +202328,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so Ma
201931
202328
  sessionDirectory,
201932
202329
  autoSearch: deps.autoSearch,
201933
202330
  cavemanTextCompression: deps.ctxReduceEnabled === false && !reducedMode ? deps.cavemanTextCompression : undefined,
202331
+ smartDrops: deps.smartDrops === true,
201934
202332
  resolvedProviderID,
201935
202333
  historyRefreshSessions: deps.historyRefreshSessions,
201936
202334
  m0M1: {
@@ -202764,6 +203162,7 @@ function maybeInjectChannel1Nudge(args, sessionId, tool, output) {
202764
203162
  const decision = decideChannel1({
202765
203163
  undroppedTokens,
202766
203164
  pressure,
203165
+ estimatedInputTokens: state.lastInputTokens + state.turnToolTokens,
202767
203166
  workingWindowTokens,
202768
203167
  lastNudgeUndropped: getLastNudgeUndropped(args.db, sessionId),
202769
203168
  lastNudgeLevel: getLastNudgeLevel(args.db, sessionId),
@@ -202829,6 +203228,7 @@ init_send_session_notification();
202829
203228
  import { createHash as createHash13 } from "node:crypto";
202830
203229
 
202831
203230
  // src/agents/magic-context-prompt.ts
203231
+ init_language_directive();
202832
203232
  var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
202833
203233
 
202834
203234
  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.
@@ -202844,16 +203244,20 @@ var PARTNER_FRAME_CLOSER_NO_REDUCE = `
202844
203244
  Context is managed for you entirely automatically — there's nothing to prune and no warnings to act on. Stay reasonably concise per operation, and never let context size change *what* work you take on or *how thoroughly* you do it.`;
202845
203245
  var CTX_NOTE_GUIDANCE = `Use \`ctx_note\` ONLY for genuinely future concerns — something to revisit much later, not work coming up in the next few turns (that's already in your active context) and not active multi-step work (use todos for that). Magic Context preserves your full context across both compaction and restarts, so an upcoming restart or "let's come back to this later" is never a reason to take a note — nothing is lost either way. Notes you do take survive compression and resurface at natural work boundaries (after commits, historian runs, todo completion).`;
202846
203246
  var TOOL_HISTORY_GUIDANCE = `Compressed history intentionally omits tool calls and their outputs — summaries like "I edited file X" are historian records, not patterns to replicate. In the live conversation, older tool calls and their results are cleaned up to save context — you may see your own past messages referencing actions without the corresponding tool call or result visible. This is normal context management. ALWAYS use real tool calls; never simulate, fabricate, or inline tool outputs in your text. If there is no tool result message, the action did not happen. NEVER simulate, hallucinate or claim tool calls, command output, search results, file edits, or diffs in plain text as if they actually occurred.`;
202847
- var BASE_INTRO = (protectedTags) => `Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
202848
- Use \`ctx_reduce\` to mark spent tagged content as discardable and reclaim space. Marking is NOT an immediate delete — it queues the content, which stays fully visible until space is actually needed (as soon as the next turn if you're already under pressure, much later if not), so mark a tool output as soon as you're done with it rather than hoarding the call for the end of the turn. The last ${protectedTags} tags are protected (marking one just queues it until it ages out). Syntax: "3-5", "1,2,9", or "1-5,8,12-15".
202849
- Do not announce or narrate \`ctx_reduce\` drops — just call the tool silently. Saying "I'll drop these outputs" wastes tokens the user does not care about.
202850
- ${CTX_NOTE_GUIDANCE}
202851
- Use \`ctx_memory\` for durable project knowledge: write what future sessions must know, update/archive/merge the memories you see in \`<project-memory>\` when they drift. Memories persist across sessions and every new session starts with them.
203247
+ var MEMORY_GUIDANCE = `Use \`ctx_memory\` for durable project knowledge: write what future sessions must know, update/archive/merge the memories you see in \`<project-memory>\` when they drift. Memories persist across sessions and every new session starts with them.
202852
203248
  **Save to memory proactively**: If you spent multiple turns finding something (a file path, a DB location, a config pattern, a workaround), save it with \`ctx_memory\` so future sessions don't repeat the search. Examples:
202853
203249
  - Found a project's source code path after searching → \`ctx_memory(action="write", category="CONFIG_VALUES", content="OpenCode source is at ~/Work/OSS/opencode")\`
202854
203250
  - Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="PROJECT_RULES", content="Always use scripts/release.sh for releases")\`
202855
- - Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
202856
- Use \`ctx_search\` to search across project memories, indexed git commits, and this session's full conversation history (including compacted parts) from one query.
203251
+ - Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\``;
203252
+ function memoryGuidanceBlock(memoryEnabled) {
203253
+ return memoryEnabled ? `${MEMORY_GUIDANCE}
203254
+ ` : "";
203255
+ }
203256
+ var BASE_INTRO = (protectedTags, memoryEnabled) => `Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
203257
+ Use \`ctx_reduce\` to mark spent tagged content as discardable and reclaim space. Marking is NOT an immediate delete — it queues the content, which stays fully visible until space is actually needed (as soon as the next turn if you're already under pressure, much later if not), so mark a tool output as soon as you're done with it rather than hoarding the call for the end of the turn. The last ${protectedTags} tags are protected (marking one just queues it until it ages out). Syntax: "3-5", "1,2,9", or "1-5,8,12-15".
203258
+ Do not announce or narrate \`ctx_reduce\` drops — just call the tool silently. Saying "I'll drop these outputs" wastes tokens the user does not care about.
203259
+ ${CTX_NOTE_GUIDANCE}
203260
+ ${memoryGuidanceBlock(memoryEnabled)}Use \`ctx_search\` to search across project memories, indexed git commits, and this session's full conversation history (including compacted parts) from one query.
202857
203261
  Use \`ctx_expand\` to recover the raw conversation behind a \`<compartment>\` summary in \`<session-history>\` — pass its \`start\`/\`end\` attributes when the summary is not enough (exact wording, values, error text).
202858
203262
  **Search before asking the user**: If you can't remember or don't know something that might have been discussed before or stored in project memory, use \`ctx_search\` before asking the user. Examples:
202859
203263
  - Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
@@ -202867,13 +203271,8 @@ NEVER drop large ranges blindly (e.g., "1-50"). Review each tag before deciding.
202867
203271
  Keep your user's instructions and intent — never drop a user message for its directive, even an old one. But a large block of pasted content inside a user message (logs, data dumps, long code, attachments) is fair to mark discardable once you've extracted what you need — it stays searchable via \`ctx_search\`.
202868
203272
  NEVER drop assistant text messages unless they are exceptionally large. Your conversation messages are lightweight; only large tool outputs are worth dropping.
202869
203273
  Before your turn finishes, consider using \`ctx_reduce\` to drop large tool outputs you no longer need.`;
202870
- var BASE_INTRO_NO_REDUCE = () => `${CTX_NOTE_GUIDANCE}
202871
- Use \`ctx_memory\` for durable project knowledge: write what future sessions must know, update/archive/merge the memories you see in \`<project-memory>\` when they drift. Memories persist across sessions and every new session starts with them.
202872
- **Save to memory proactively**: If you spent multiple turns finding something (a file path, a DB location, a config pattern, a workaround), save it with \`ctx_memory\` so future sessions don't repeat the search. Examples:
202873
- - Found a project's source code path after searching → \`ctx_memory(action="write", category="CONFIG_VALUES", content="OpenCode source is at ~/Work/OSS/opencode")\`
202874
- - Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="PROJECT_RULES", content="Always use scripts/release.sh for releases")\`
202875
- - Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
202876
- Use \`ctx_search\` to search across project memories, indexed git commits, and this session's full conversation history (including compacted parts) from one query.
203274
+ var BASE_INTRO_NO_REDUCE = (memoryEnabled) => `${CTX_NOTE_GUIDANCE}
203275
+ ${memoryGuidanceBlock(memoryEnabled)}Use \`ctx_search\` to search across project memories, indexed git commits, and this session's full conversation history (including compacted parts) from one query.
202877
203276
  Use \`ctx_expand\` to recover the raw conversation behind a \`<compartment>\` summary in \`<session-history>\` — pass its \`start\`/\`end\` attributes when the summary is not enough (exact wording, values, error text).
202878
203277
  **Search before asking the user**: If you can't remember or don't know something that might have been discussed before or stored in project memory, use \`ctx_search\` before asking the user. Examples:
202879
203278
  - Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
@@ -202907,7 +203306,7 @@ Drop silently — do not narrate it. NEVER drop large ranges blindly (e.g., "1-5
202907
203306
  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.`;
202908
203307
  var CAVEMAN_COMPRESSION_WARNING = `
202909
203308
  **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.`;
202910
- function buildMagicContextSection(_agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false) {
203309
+ function buildMagicContextSection(_agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language, memoryEnabled = true) {
202911
203310
  if (subagentMode) {
202912
203311
  return `## Magic Context
202913
203312
 
@@ -202919,23 +203318,27 @@ The dreamer evaluates smart note conditions during nightly runs and surfaces the
202919
203318
  Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`` : "";
202920
203319
  const temporalGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : "";
202921
203320
  const cavemanWarning = cavemanTextCompressionEnabled && !ctxReduceEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
203321
+ const languageDirective = buildPrimaryLanguageDirective(language);
203322
+ const languageGuidance = languageDirective ? `
203323
+
203324
+ ${languageDirective}` : "";
202922
203325
  if (!ctxReduceEnabled) {
202923
203326
  return `## Magic Context
202924
203327
 
202925
203328
  ${LONG_TERM_PARTNER_FRAME}
202926
203329
  ${PARTNER_FRAME_CLOSER_NO_REDUCE}
202927
203330
 
202928
- ${BASE_INTRO_NO_REDUCE()}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}`;
203331
+ ${BASE_INTRO_NO_REDUCE(memoryEnabled)}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}${languageGuidance}`;
202929
203332
  }
202930
203333
  return `## Magic Context
202931
203334
 
202932
203335
  ${LONG_TERM_PARTNER_FRAME}
202933
203336
  ${PARTNER_FRAME_CLOSER_REDUCE}
202934
203337
 
202935
- ${BASE_INTRO(protectedTags)}${smartNoteGuidance}${temporalGuidance}
203338
+ ${BASE_INTRO(protectedTags, memoryEnabled)}${smartNoteGuidance}${temporalGuidance}
202936
203339
  ${GENERIC_SECTION}
202937
203340
 
202938
- Prefer many small targeted operations over one large blanket operation, and keep the working set tidy as routine maintenance.`;
203341
+ Prefer many small targeted operations over one large blanket operation, and keep the working set tidy as routine maintenance.${languageGuidance}`;
202939
203342
  }
202940
203343
 
202941
203344
  // src/hooks/magic-context/system-prompt-hash.ts
@@ -202993,7 +203396,7 @@ function createSystemPromptHashHandler(deps) {
202993
203396
  const fullPrompt = output.system.join(`
202994
203397
  `);
202995
203398
  if (fullPrompt.length > 0 && !fullPrompt.includes(MAGIC_CONTEXT_MARKER) && !skipGuidanceForDisabledSubagent) {
202996
- const guidance = buildMagicContextSection(null, deps.protectedTags, effectiveCtxReduceEnabled, deps.dreamerEnabled, deps.experimentalTemporalAwareness, deps.experimentalCavemanTextCompression, subagentReduceMode);
203399
+ const guidance = buildMagicContextSection(null, deps.protectedTags, effectiveCtxReduceEnabled, deps.dreamerEnabled, deps.experimentalTemporalAwareness, deps.experimentalCavemanTextCompression, subagentReduceMode, deps.language, deps.memoryEnabled !== false);
202997
203400
  output.system.push(guidance);
202998
203401
  sessionLog(sessionId, `injected generic guidance into system prompt (ctxReduce=${effectiveCtxReduceEnabled}, subagent=${isSubagentSession}, subagentReduceMode=${subagentReduceMode})`);
202999
203402
  }
@@ -203281,6 +203684,7 @@ function createMagicContextHook(deps) {
203281
203684
  memoryEnabled: deps.config.memory?.enabled ?? true,
203282
203685
  autoPromote: deps.config.memory?.auto_promote ?? true,
203283
203686
  fallbackModels: historianFallbackModels,
203687
+ language: deps.config.language,
203284
203688
  fallbackModelId: (() => {
203285
203689
  const model = resolveLiveModel(sessionId);
203286
203690
  return model ? `${model.providerID}/${model.modelID}` : undefined;
@@ -203319,7 +203723,7 @@ function createMagicContextHook(deps) {
203319
203723
  signal,
203320
203724
  onProgress: ({ embedded, total }) => {
203321
203725
  const cur = recompProgressBySession.get(sessionId);
203322
- if (!cur || cur.phase !== "recomp")
203726
+ if (cur?.phase !== "recomp")
203323
203727
  return;
203324
203728
  recompProgressBySession.set(sessionId, {
203325
203729
  ...cur,
@@ -203430,6 +203834,7 @@ function createMagicContextHook(deps) {
203430
203834
  channel1StateBySession,
203431
203835
  protectedTags: deps.config.protected_tags,
203432
203836
  ctxReduceEnabled,
203837
+ smartDrops: deps.config.smart_drops === true,
203433
203838
  clearReasoningAge: deps.config.clear_reasoning_age ?? 50,
203434
203839
  commitClusterTrigger: deps.config.commit_cluster_trigger,
203435
203840
  historyRefreshSessions,
@@ -203522,7 +203927,7 @@ function createMagicContextHook(deps) {
203522
203927
  return;
203523
203928
  }
203524
203929
  lastScheduleCheckMs = now;
203525
- const runtimeConfigs = buildDreamTaskRuntimeConfigs(dreaming);
203930
+ const runtimeConfigs = buildDreamTaskRuntimeConfigs(dreaming, deps.config.language);
203526
203931
  const executor = createDreamTaskExecutor({
203527
203932
  client: deps.client,
203528
203933
  sessionDirectory: deps.directory,
@@ -203531,7 +203936,8 @@ function createMagicContextHook(deps) {
203531
203936
  contextDb: providerDb,
203532
203937
  openOpenCodeDb
203533
203938
  }),
203534
- userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreaming)
203939
+ userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreaming),
203940
+ language: deps.config.language
203535
203941
  });
203536
203942
  runDueTasksForProject({
203537
203943
  db,
@@ -203580,7 +203986,8 @@ function createMagicContextHook(deps) {
203580
203986
  config: sidekickConfig,
203581
203987
  projectPath,
203582
203988
  sessionDirectory: deps.directory,
203583
- client: deps.client
203989
+ client: deps.client,
203990
+ language: deps.config.language
203584
203991
  } : undefined,
203585
203992
  dreamer: dreamerConfig ? {
203586
203993
  config: dreamerConfig,
@@ -203588,7 +203995,7 @@ function createMagicContextHook(deps) {
203588
203995
  runManual: (task) => runManualDream({
203589
203996
  db,
203590
203997
  projectIdentity: projectPath,
203591
- tasks: buildDreamTaskRuntimeConfigs(dreamerConfig),
203998
+ tasks: buildDreamTaskRuntimeConfigs(dreamerConfig, deps.config.language),
203592
203999
  executor: createDreamTaskExecutor({
203593
204000
  client: deps.client,
203594
204001
  sessionDirectory: deps.directory,
@@ -203597,7 +204004,8 @@ function createMagicContextHook(deps) {
203597
204004
  contextDb: providerDb,
203598
204005
  openOpenCodeDb
203599
204006
  }),
203600
- userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreamerConfig)
204007
+ userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreamerConfig),
204008
+ language: deps.config.language
203601
204009
  }),
203602
204010
  task
203603
204011
  })
@@ -203608,6 +204016,8 @@ function createMagicContextHook(deps) {
203608
204016
  protectedTags: deps.config.protected_tags,
203609
204017
  ctxReduceEnabled,
203610
204018
  dreamerEnabled: dreamerRunnable,
204019
+ memoryEnabled: deps.config.memory?.enabled !== false,
204020
+ language: deps.config.language,
203611
204021
  injectDocs: deps.config.dreamer?.inject_docs !== false,
203612
204022
  directory: deps.directory,
203613
204023
  historyRefreshSessions,
@@ -205993,6 +206403,7 @@ function createToolRegistry(args) {
205993
206403
  ensureProjectRegisteredFromOpenCodeDirectory(ctx.directory, db);
205994
206404
  const resolveProjectPath = (directory) => resolveProjectIdentity(directory);
205995
206405
  const ctxReduceEnabled = pluginConfig.ctx_reduce_enabled !== false;
206406
+ const memoryEnabled = pluginConfig.memory?.enabled !== false;
205996
206407
  const allTools = {
205997
206408
  ...ctxReduceEnabled ? createCtxReduceTools({
205998
206409
  db,
@@ -206009,12 +206420,12 @@ function createToolRegistry(args) {
206009
206420
  resolveProjectPath,
206010
206421
  ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory
206011
206422
  }),
206012
- ...createCtxMemoryTools({
206423
+ ...memoryEnabled ? createCtxMemoryTools({
206013
206424
  db,
206014
206425
  resolveProjectPath,
206015
206426
  ensureProjectRegistered: ensureProjectRegisteredFromOpenCodeDirectory,
206016
206427
  allowedActions: [...CTX_MEMORY_ACTIONS]
206017
- })
206428
+ }) : {}
206018
206429
  };
206019
206430
  for (const toolDefinition of Object.values(allTools)) {
206020
206431
  normalizeToolArgSchemas(toolDefinition);
@@ -206339,6 +206750,7 @@ var server2 = async (ctx) => {
206339
206750
  projectIdentity: resolveProjectIdentity(ctx.directory),
206340
206751
  client: ctx.client,
206341
206752
  dreamerConfig: dreamerRunnable ? pluginConfig.dreamer : undefined,
206753
+ language: pluginConfig.language,
206342
206754
  embeddingConfig: pluginConfig.embedding,
206343
206755
  memoryEnabled: pluginConfig.memory?.enabled === true,
206344
206756
  gitCommitIndexing: pluginConfig.memory.git_commit_indexing?.enabled ? {
@@ -206510,9 +206922,9 @@ var server2 = async (ctx) => {
206510
206922
  const registrations = buildHiddenAgentRegistrations({
206511
206923
  dreamerPrompt: DREAMER_SYSTEM_PROMPT,
206512
206924
  smartNoteCompilerPrompt: SMART_NOTE_COMPILER_SYSTEM_PROMPT,
206513
- historianPrompt: COMPARTMENT_AGENT_SYSTEM_PROMPT,
206514
- historianRecompPrompt: COMPARTMENT_STRUCTURAL_SYSTEM_PROMPT,
206515
- historianEditorPrompt: HISTORIAN_EDITOR_SYSTEM_PROMPT,
206925
+ historianPrompt: withContentLanguageDirective(COMPARTMENT_AGENT_SYSTEM_PROMPT, pluginConfig.language, { preserveUserQuotes: true }),
206926
+ historianRecompPrompt: withContentLanguageDirective(COMPARTMENT_STRUCTURAL_SYSTEM_PROMPT, pluginConfig.language, { preserveUserQuotes: true }),
206927
+ historianEditorPrompt: withContentLanguageDirective(HISTORIAN_EDITOR_SYSTEM_PROMPT, pluginConfig.language, { preserveUserQuotes: true }),
206516
206928
  sidekickPrompt: SIDEKICK_SYSTEM_PROMPT,
206517
206929
  dreamerOverrides: dreamerAgentOverrides,
206518
206930
  historianOverrides: historianAgentOverrides,