@cortexkit/opencode-magic-context 0.12.0 → 0.13.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 (65) hide show
  1. package/README.md +3 -1
  2. package/dist/agents/historian.d.ts +1 -0
  3. package/dist/agents/historian.d.ts.map +1 -1
  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/cli/doctor.d.ts.map +1 -1
  7. package/dist/cli.js +362 -83
  8. package/dist/config/index.d.ts.map +1 -1
  9. package/dist/config/schema/magic-context.d.ts +103 -2
  10. package/dist/config/schema/magic-context.d.ts.map +1 -1
  11. package/dist/config/variable.d.ts +46 -0
  12. package/dist/config/variable.d.ts.map +1 -0
  13. package/dist/features/magic-context/compartment-storage.d.ts +27 -1
  14. package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
  15. package/dist/features/magic-context/compression-depth-storage.d.ts +7 -0
  16. package/dist/features/magic-context/compression-depth-storage.d.ts.map +1 -1
  17. package/dist/features/magic-context/memory/embedding-probe.d.ts +69 -0
  18. package/dist/features/magic-context/memory/embedding-probe.d.ts.map +1 -0
  19. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  20. package/dist/hooks/magic-context/caveman.d.ts +35 -0
  21. package/dist/hooks/magic-context/caveman.d.ts.map +1 -0
  22. package/dist/hooks/magic-context/command-handler.d.ts +23 -1
  23. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  24. package/dist/hooks/magic-context/compartment-prompt.d.ts +8 -3
  25. package/dist/hooks/magic-context/compartment-prompt.d.ts.map +1 -1
  26. package/dist/hooks/magic-context/compartment-runner-compressor.d.ts +46 -0
  27. package/dist/hooks/magic-context/compartment-runner-compressor.d.ts.map +1 -1
  28. package/dist/hooks/magic-context/compartment-runner-historian.d.ts +10 -0
  29. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  30. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  31. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts +29 -0
  32. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -0
  33. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  34. package/dist/hooks/magic-context/compartment-runner-types.d.ts +9 -0
  35. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  36. package/dist/hooks/magic-context/compartment-runner-validation.d.ts +5 -0
  37. package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/compartment-runner.d.ts +31 -1
  39. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/hook.d.ts +9 -4
  41. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/inject-compartments.d.ts +1 -1
  43. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/read-session-chunk.d.ts +11 -0
  45. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  46. package/dist/hooks/magic-context/read-session-db.d.ts +13 -0
  47. package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/read-session-formatting.d.ts +7 -0
  49. package/dist/hooks/magic-context/read-session-formatting.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/system-prompt-hash.d.ts +2 -0
  51. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  52. package/dist/hooks/magic-context/temporal-awareness.d.ts +73 -0
  53. package/dist/hooks/magic-context/temporal-awareness.d.ts.map +1 -0
  54. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +10 -0
  55. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  56. package/dist/hooks/magic-context/transform.d.ts +14 -0
  57. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +2415 -1064
  60. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  61. package/dist/shared/model-requirements.d.ts.map +1 -1
  62. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  63. package/package.json +1 -1
  64. package/src/shared/model-requirements.ts +3 -1
  65. package/src/shared/models-dev-cache.ts +11 -1
package/dist/index.js CHANGED
@@ -49,7 +49,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
49
49
  var DREAMER_AGENT = "dreamer";
50
50
 
51
51
  // src/agents/historian.ts
52
- var HISTORIAN_AGENT = "historian";
52
+ var HISTORIAN_AGENT = "historian", HISTORIAN_EDITOR_AGENT = "historian-editor";
53
53
 
54
54
  // src/agents/sidekick.ts
55
55
  var SIDEKICK_AGENT = "sidekick";
@@ -14070,10 +14070,17 @@ var init_agent_overrides = __esm(() => {
14070
14070
  });
14071
14071
 
14072
14072
  // src/config/schema/magic-context.ts
14073
- var DEFAULT_NUDGE_INTERVAL_TOKENS = 1e4, DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65, DEFAULT_HISTORIAN_TIMEOUT_MS = 300000, DEFAULT_HISTORY_BUDGET_PERCENTAGE = 0.15, DEFAULT_LOCAL_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2", DREAMER_TASKS, DreamingTaskSchema, DEFAULT_DREAMER_TASKS, DreamerConfigSchema, SidekickConfigSchema, BaseEmbeddingConfigSchema, EmbeddingConfigSchema, MagicContextConfigSchema;
14073
+ var DEFAULT_NUDGE_INTERVAL_TOKENS = 1e4, DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65, DEFAULT_HISTORIAN_TIMEOUT_MS = 300000, DEFAULT_HISTORY_BUDGET_PERCENTAGE = 0.15, DEFAULT_LOCAL_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2", DEFAULT_COMPRESSOR_MIN_COMPARTMENT_RATIO = 1000, DEFAULT_COMPRESSOR_MAX_MERGE_DEPTH = 5, DEFAULT_COMPRESSOR_COOLDOWN_MS = 600000, DEFAULT_COMPRESSOR_MAX_COMPARTMENTS_PER_PASS = 15, DEFAULT_COMPRESSOR_GRACE_COMPARTMENTS = 10, COMPRESSOR_MERGE_RATIO_BY_DEPTH, DREAMER_TASKS, DreamingTaskSchema, DEFAULT_DREAMER_TASKS, DreamerConfigSchema, SidekickConfigSchema, HistorianConfigSchema, BaseEmbeddingConfigSchema, EmbeddingConfigSchema, MagicContextConfigSchema;
14074
14074
  var init_magic_context = __esm(() => {
14075
14075
  init_zod();
14076
14076
  init_agent_overrides();
14077
+ COMPRESSOR_MERGE_RATIO_BY_DEPTH = {
14078
+ 1: 1.33,
14079
+ 2: 1.5,
14080
+ 3: 2,
14081
+ 4: 2,
14082
+ 5: 0
14083
+ };
14077
14084
  DREAMER_TASKS = [
14078
14085
  "consolidate",
14079
14086
  "verify",
@@ -14101,6 +14108,9 @@ var init_magic_context = __esm(() => {
14101
14108
  timeout_ms: exports_external.number().default(30000),
14102
14109
  system_prompt: exports_external.string().optional()
14103
14110
  }).optional();
14111
+ HistorianConfigSchema = AgentOverrideConfigSchema.extend({
14112
+ two_pass: exports_external.boolean().default(false)
14113
+ }).optional();
14104
14114
  BaseEmbeddingConfigSchema = exports_external.object({
14105
14115
  provider: exports_external.enum(["local", "openai-compatible", "off"]).default("local"),
14106
14116
  model: exports_external.string().optional(),
@@ -14143,7 +14153,7 @@ var init_magic_context = __esm(() => {
14143
14153
  MagicContextConfigSchema = exports_external.object({
14144
14154
  enabled: exports_external.boolean().default(true),
14145
14155
  ctx_reduce_enabled: exports_external.boolean().default(true),
14146
- historian: AgentOverrideConfigSchema.optional(),
14156
+ historian: HistorianConfigSchema,
14147
14157
  dreamer: DreamerConfigSchema.optional(),
14148
14158
  cache_ttl: exports_external.union([exports_external.string(), exports_external.object({ default: exports_external.string() }).catchall(exports_external.string())]).default("5m"),
14149
14159
  nudge_interval_tokens: exports_external.number().min(1000).default(DEFAULT_NUDGE_INTERVAL_TOKENS),
@@ -14166,6 +14176,21 @@ var init_magic_context = __esm(() => {
14166
14176
  min_clusters: exports_external.number().min(1).default(3)
14167
14177
  }).default({ enabled: true, min_clusters: 3 }),
14168
14178
  compaction_markers: exports_external.boolean().default(true),
14179
+ compressor: exports_external.object({
14180
+ enabled: exports_external.boolean().default(true),
14181
+ min_compartment_ratio: exports_external.number().min(100).max(1e4).default(DEFAULT_COMPRESSOR_MIN_COMPARTMENT_RATIO),
14182
+ max_merge_depth: exports_external.number().min(1).max(5).default(DEFAULT_COMPRESSOR_MAX_MERGE_DEPTH),
14183
+ cooldown_ms: exports_external.number().min(60000).default(DEFAULT_COMPRESSOR_COOLDOWN_MS),
14184
+ max_compartments_per_pass: exports_external.number().min(3).max(50).default(DEFAULT_COMPRESSOR_MAX_COMPARTMENTS_PER_PASS),
14185
+ grace_compartments: exports_external.number().min(0).max(100).default(DEFAULT_COMPRESSOR_GRACE_COMPARTMENTS)
14186
+ }).default({
14187
+ enabled: true,
14188
+ min_compartment_ratio: DEFAULT_COMPRESSOR_MIN_COMPARTMENT_RATIO,
14189
+ max_merge_depth: DEFAULT_COMPRESSOR_MAX_MERGE_DEPTH,
14190
+ cooldown_ms: DEFAULT_COMPRESSOR_COOLDOWN_MS,
14191
+ max_compartments_per_pass: DEFAULT_COMPRESSOR_MAX_COMPARTMENTS_PER_PASS,
14192
+ grace_compartments: DEFAULT_COMPRESSOR_GRACE_COMPARTMENTS
14193
+ }),
14169
14194
  embedding: EmbeddingConfigSchema.default({
14170
14195
  provider: "local",
14171
14196
  model: DEFAULT_LOCAL_EMBEDDING_MODEL
@@ -14179,10 +14204,12 @@ var init_magic_context = __esm(() => {
14179
14204
  enabled: exports_external.boolean().default(false),
14180
14205
  token_budget: exports_external.number().min(2000).max(30000).default(1e4),
14181
14206
  min_reads: exports_external.number().min(2).max(20).default(4)
14182
- }).default({ enabled: false, token_budget: 1e4, min_reads: 4 })
14207
+ }).default({ enabled: false, token_budget: 1e4, min_reads: 4 }),
14208
+ temporal_awareness: exports_external.boolean().default(false)
14183
14209
  }).default({
14184
14210
  user_memories: { enabled: false, promotion_threshold: 3 },
14185
- pin_key_files: { enabled: false, token_budget: 1e4, min_reads: 4 }
14211
+ pin_key_files: { enabled: false, token_budget: 1e4, min_reads: 4 },
14212
+ temporal_awareness: false
14186
14213
  }),
14187
14214
  memory: exports_external.object({
14188
14215
  enabled: exports_external.boolean().default(true),
@@ -14303,6 +14330,7 @@ var init_model_requirements = __esm(() => {
14303
14330
  ];
14304
14331
  AGENT_MODEL_REQUIREMENTS = {
14305
14332
  [HISTORIAN_AGENT]: { fallbackChain: HISTORIAN_FALLBACK_CHAIN },
14333
+ [HISTORIAN_EDITOR_AGENT]: { fallbackChain: HISTORIAN_FALLBACK_CHAIN },
14306
14334
  [DREAMER_AGENT]: { fallbackChain: DREAMER_FALLBACK_CHAIN },
14307
14335
  [SIDEKICK_AGENT]: { fallbackChain: SIDEKICK_FALLBACK_CHAIN }
14308
14336
  };
@@ -14592,14 +14620,16 @@ function replaceAllCompartmentState(db, sessionId, compartments, facts) {
14592
14620
  db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE session_id = ?").run(sessionId);
14593
14621
  })();
14594
14622
  }
14595
- function buildCompartmentBlock(compartments, facts, memoryBlock) {
14623
+ function buildCompartmentBlock(compartments, facts, memoryBlock, dateRanges) {
14596
14624
  const lines = [];
14597
14625
  if (memoryBlock) {
14598
14626
  lines.push(memoryBlock);
14599
14627
  lines.push("");
14600
14628
  }
14601
14629
  for (const c of compartments) {
14602
- lines.push(`<compartment start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr(c.title)}">`);
14630
+ const dates = dateRanges?.byId.get(c.id);
14631
+ const dateAttr = dates ? ` start-date="${dates.start}" end-date="${dates.end}"` : "";
14632
+ lines.push(`<compartment start="${c.startMessage}" end="${c.endMessage}"${dateAttr} title="${escapeXmlAttr(c.title)}">`);
14603
14633
  lines.push(escapeXmlContent(c.content));
14604
14634
  lines.push("</compartment>");
14605
14635
  lines.push("");
@@ -14682,8 +14712,29 @@ function clearRecompStaging(db, sessionId) {
14682
14712
  db.transaction(() => {
14683
14713
  db.prepare("DELETE FROM recomp_compartments WHERE session_id = ?").run(sessionId);
14684
14714
  db.prepare("DELETE FROM recomp_facts WHERE session_id = ?").run(sessionId);
14715
+ try {
14716
+ db.prepare("UPDATE session_meta SET recomp_partial_range_start = 0, recomp_partial_range_end = 0 WHERE session_id = ?").run(sessionId);
14717
+ } catch {}
14685
14718
  })();
14686
14719
  }
14720
+ function getRecompPartialRange(db, sessionId) {
14721
+ try {
14722
+ const row = db.prepare("SELECT recomp_partial_range_start AS start, recomp_partial_range_end AS end FROM session_meta WHERE session_id = ?").get(sessionId);
14723
+ const start = typeof row?.start === "number" ? row.start : 0;
14724
+ const end = typeof row?.end === "number" ? row.end : 0;
14725
+ if (start <= 0 || end <= 0)
14726
+ return null;
14727
+ return { start, end };
14728
+ } catch {
14729
+ return null;
14730
+ }
14731
+ }
14732
+ function setRecompPartialRange(db, sessionId, range) {
14733
+ const start = range ? range.start : 0;
14734
+ const end = range ? range.end : 0;
14735
+ db.prepare("INSERT OR IGNORE INTO session_meta (session_id) VALUES (?)").run(sessionId);
14736
+ db.prepare("UPDATE session_meta SET recomp_partial_range_start = ?, recomp_partial_range_end = ? WHERE session_id = ?").run(start, end, sessionId);
14737
+ }
14687
14738
  function isRecompCompartmentRow(row) {
14688
14739
  if (row === null || typeof row !== "object")
14689
14740
  return false;
@@ -14709,23 +14760,36 @@ var init_compartment_storage = __esm(() => {
14709
14760
  });
14710
14761
 
14711
14762
  // src/hooks/magic-context/compartment-prompt.ts
14712
- function buildCompressorPrompt(compartments, currentTokens, targetTokens, averageDepth = 0) {
14763
+ function buildHistorianEditorPrompt(draft) {
14764
+ return [
14765
+ "This is a historian draft. Clean it up following the rules in your system prompt.",
14766
+ "",
14767
+ "<draft>",
14768
+ draft,
14769
+ "</draft>",
14770
+ "",
14771
+ "Return the cleaned draft as valid XML matching the original structure."
14772
+ ].join(`
14773
+ `);
14774
+ }
14775
+ function buildCompressorPrompt(compartments, currentTokens, targetTokens, outputDepth, outputCount) {
14713
14776
  const lines = [];
14714
- lines.push(`These ${compartments.length} compartments use approximately ${currentTokens} tokens. Compress them to approximately ${targetTokens} tokens.`);
14777
+ const densityLabel = outputDepth === 1 ? "MERGE ONLY" : outputDepth === 2 ? "LITE TIGHTEN" : outputDepth === 3 ? "FULL CONDENSE" : "ULTRA TELEGRAPH";
14778
+ const resolvedOutputCount = outputCount ?? Math.max(1, Math.ceil(compartments.length / 2));
14779
+ lines.push(`Density target: LEVEL ${outputDepth} (${densityLabel}). See system prompt for level rules.`);
14780
+ lines.push(`Input: ${compartments.length} compartments, ~${currentTokens} tokens. Target output: exactly ${resolvedOutputCount} compartments, ~${targetTokens} tokens total.`);
14715
14781
  lines.push("");
14716
- if (averageDepth < 2) {
14717
- lines.push("These compartments contain U: lines showing what the user asked. Preserve ALL U: lines exactly as they are. Focus compression on the narrative prose \u2014 merge compartments, remove redundancy, condense descriptions. Do not modify or remove U: lines.");
14718
- } else if (averageDepth < 3) {
14719
- lines.push("These compartments have already been compressed multiple times. Condense each U: line to its core intent in one sentence, but keep them as separate U: lines. Focus aggressive compression on the narrative prose.");
14782
+ if (outputDepth === 1) {
14783
+ lines.push("Merge only. Preserve narrative and all U: lines. Drop only genuine duplicate sentences spanning compartments.");
14784
+ } else if (outputDepth === 2) {
14785
+ lines.push("Merge + drop filler words and hedging. Keep grammar, keep U: lines verbatim.");
14786
+ } else if (outputDepth === 3) {
14787
+ lines.push("Merge into single-paragraph compartments. Drop articles and weak auxiliaries. Keep only IRREPLACEABLE U: lines.");
14720
14788
  } else {
14721
- lines.push("These compartments have been heavily compressed. Fold any remaining U: user intent into the narrative prose \u2014 do not keep separate U: lines. Write fluent summary paragraphs that naturally capture both what was asked and what was done.");
14789
+ lines.push("Merge into telegraphic fragments with symbol connectives (\u2192 + // |). U: lines only if truly irreplaceable.");
14722
14790
  }
14723
14791
  lines.push("");
14724
- lines.push("Rules:");
14725
- lines.push("- Merge adjacent compartments when they cover related work.");
14726
- lines.push("- Each output compartment must use the exact start/end ordinals from the input compartments it covers.");
14727
- lines.push("- Do not invent new ordinal boundaries that don't exist in the input.");
14728
- lines.push("- Preserve commit hashes and key technical details where possible.");
14792
+ lines.push("Preserved literally at all levels: commit hashes, file paths, URLs, code spans.");
14729
14793
  lines.push("");
14730
14794
  for (const c of compartments) {
14731
14795
  lines.push(`<compartment start="${c.startMessage}" end="${c.endMessage}" title="${escapeXmlAttr(c.title)}">`);
@@ -14733,7 +14797,7 @@ function buildCompressorPrompt(compartments, currentTokens, targetTokens, averag
14733
14797
  lines.push("</compartment>");
14734
14798
  lines.push("");
14735
14799
  }
14736
- lines.push("Return compressed compartments as XML.");
14800
+ lines.push("Return merged compartments as XML.");
14737
14801
  return lines.join(`
14738
14802
  `);
14739
14803
  }
@@ -14767,29 +14831,144 @@ var COMPARTMENT_AGENT_SYSTEM_PROMPT = `You condense long AI coding sessions into
14767
14831
  Compartment rules:
14768
14832
  - A compartment is one contiguous completed work unit: investigation, fix, refactor, docs update, feature, or decision.
14769
14833
  - Start a new compartment only when the work clearly pivots to a different objective.
14834
+ - If one broad effort contains multiple completed sub-pivots with distinct outcomes, prefer multiple smaller compartments over one umbrella compartment with many U: lines.
14770
14835
  - Do not create compartments for magic-context commands or tool-only noise.
14771
14836
  - If the input ends mid-topic, leave it out and report its first message index in <unprocessed_from>.
14772
14837
  - All compartment start/end ordinals and <unprocessed_from> must use the absolute raw message numbers shown in the input. Never renumber relative to this chunk.
14838
+ - Every displayed raw message ordinal in the input MUST appear in exactly one compartment. Gaps between compartments are invalid. When a displayed block is pure tool noise (e.g. a long "TC: ..." run with no narrative text), do NOT skip it \u2014 extend the preceding compartment's \`end\` to absorb the range, or include it inside the current compartment if the block falls within an ongoing work unit. Never create a dedicated compartment just to cover a tool-only run.
14773
14839
  - Only emit NEW compartments for the new messages. Do not re-emit existing compartments from the existing state.
14774
14840
  - Write comprehensive, detailed compartments. Include file paths, function names, commit hashes, config keys, and values when they matter.
14775
14841
  - Do not list every changed file. Do not narrate tool calls. Do not preserve dead-end exploration beyond a brief clause when needed.
14776
14842
 
14777
- User message preservation:
14778
- - Include high-signal user messages verbatim inside compartments, prefixed with U:.
14779
- - A high-signal user message states a goal, constraint, design decision, direction, preference, or rationale.
14780
- - Drop trivial messages: yes, continue, I agree, thanks, looks good, go ahead, and similar low-signal steering.
14781
- - Drop large pasted text unless it contains durable rules or requirements; summarize its gist instead.
14782
- - Place U: lines at the point in the summary where the user's direction changed the work.
14783
- - Limit to 3-5 U: lines per compartment \u2014 keep only the most important ones.
14784
-
14785
- Compartment example:
14786
- <compartment start="50" end="120" title="Built the LSP stack">
14787
- U: We need inline diagnostics on every edit, not just on-demand
14788
- Implemented in-process LSP client with per-server reader threads and crossbeam-channel delivery. Added inline edit diagnostics to write, edit, and apply_patch. commits: a3f891, b22c4e
14789
- U: Ship this as 0.2.0
14790
- Updated docs and publish automation, released v0.2.0.
14843
+ # Construction order (MANDATORY)
14844
+
14845
+ For each compartment, build in this exact order:
14846
+
14847
+ 1. Write the narrative summary first \u2014 what was done, why, and the outcome. This is 1\u20134 sentences covering the work unit completely.
14848
+ 2. Re-read your narrative. Ask: does the summary already convey all important decisions and constraints from this work unit?
14849
+ 3. If yes, the compartment is DONE with zero U: lines. Move on.
14850
+ 4. If no, identify the specific signal the narrative cannot capture. Add U: lines ONLY for those signals.
14851
+ 5. Before writing each U: line, run the CROSS-COMPARTMENT CHECK (see below).
14852
+
14853
+ Zero U: lines in a compartment is normal and expected. Most compartments should have 0\u20132 U: lines. Compartments with 3\u20135 are rare and must be justified by genuinely distinct durable signals.
14854
+
14855
+ # DROP rules (check these first \u2014 if any match, drop without exception)
14856
+
14857
+ - Questions in ANY form: "should I X?", "what about Y?", "do you think Z?", "isn't it better to A?", "why don't we B?", "any ideas?" \u2014 the resolved answer belongs in narrative only. If it feels important to keep the question, you are wrong: keep the answer in narrative.
14858
+ - Agreements and acknowledgments: "yes", "okay", "sure", "thanks", "go ahead", "looks good", "perfect", "I agree", "sounds good", "great".
14859
+ - Pure pacing and sequencing: "let's start", "continue", "let's do all", "now we can X", "let's commit", "first do A then B", "before that", "in the meantime".
14860
+ - Tactical observations: "I just noticed X", "we recently did Y", "I'm seeing Z right now", "this seems wrong".
14861
+ - Debugging status: "context is at 78%", "I'm restarting", "the last build failed".
14862
+ - Dogfooding/restart loops: "I restarted, can you check?", "okay we should have updated versions now", "let me try again".
14863
+ - Pasted error output or logs as U: line \u2014 capture the underlying problem in narrative, not the raw paste.
14864
+ - Examples and illustrations: "mine was when an agent wants to see X" \u2014 convert the underlying intent into a directive or drop.
14865
+ - Hype with embedded directive: ALL-CAPS pleas, "PLEASE PLEASE PLEASE just do X" \u2014 extract only the underlying directive into narrative; drop the hype.
14866
+ - Social signals, banter, emoji-only, enthusiasm.
14867
+ - Deferred ideas: "for later", "we can do X later", "another idea for the future".
14868
+ - Mid-process status: "running Y", "checking Z".
14869
+ - Superseded drafts once a later message gives the final decision.
14870
+ - Standing workflow rules ("always run lint before push") \u2014 these belong in WORKFLOW_RULES facts, not U: lines.
14871
+
14872
+ # Wording rule (default: verbatim)
14873
+
14874
+ By default, U: lines use the user's actual wording. The user's exact phrasing often carries negotiation context, emphasis, or technical specificity that paraphrase loses.
14875
+
14876
+ Paraphrase ONLY in these cases:
14877
+ - **Strip agreement prefixes**: "Yes X", "Okay X", "Sure X" \u2192 keep only the substantive part of X, in the user's original wording.
14878
+ - **Split compound directives**: If one message contains two distinct durable directives, split into two U: lines \u2014 each preserving the user's wording for its part.
14879
+ - **Drop conversational noise, keep core**: If a message wraps a directive in exploratory phrasing ("so I was thinking, maybe... but actually..."), drop the exploration and keep the core directive in the user's remaining words. Don't invent new phrasing.
14880
+
14881
+ NEVER:
14882
+ - Rewrite a clear user directive into a formal constraint statement. ("We need tool count at ~8" stays as-is; do NOT convert to "Tool count must be capped at 8.")
14883
+ - Synthesize a directive from multiple messages into one canonical statement. If the signal needs synthesis, it belongs in narrative, not a U: line.
14884
+ - Add technical specificity the user didn't state (file paths, function names, constant names). Canonical technical specificity belongs in narrative or facts, not in U: lines attributed to the user.
14885
+
14886
+ Good example:
14887
+ Original user message: "Yes let's do this. But we need to also make sure that we limit by message count as some sessions have quite a lot of messages."
14888
+ Correct U: line: "We need to also make sure that we limit by message count as some sessions have quite a lot of messages."
14889
+ (Stripped agreement prefix; kept the user's actual wording.)
14890
+
14891
+ Bad example (do not do this):
14892
+ Incorrect U: line: "Cap session history retrieval at a maximum message count to prevent memory issues on large sessions."
14893
+ (Rewrote the user directive into formal language and invented specificity.)
14894
+
14895
+ # KEEP rules (U: line survives only if ALL pass)
14896
+
14897
+ 1. DURABLE: The signal matters after the immediate turn.
14898
+ 2. SPECIFIC: Concrete goal, hard constraint, design decision, rejection, rationale, threshold, source-of-truth correction, or future-work directive.
14899
+ 3. OUTCOME-BACKED: This compartment's narrative clearly states what was done, decided, or changed because of the message.
14900
+ 4. NON-REDUNDANT: Not captured by another U: line (see CROSS-COMPARTMENT CHECK), by a fact, or by the narrative.
14901
+ 5. IRREPLACEABLE: The user's wording adds signal that narrative paraphrase cannot preserve. If the same information could appear as narrative without losing meaning, it should.
14902
+
14903
+ Categories of KEEP:
14904
+ - Hard gates, thresholds, config defaults, percentages, byte sizes with concrete values.
14905
+ - Accepted designs and explicit decisions.
14906
+ - Rejections and negative constraints: "X is wrong because Y", "we should NOT do Z".
14907
+ - Source-of-truth corrections: "follow the code, not the README".
14908
+ - Implementation pivots stated in future tense: "instead of X let's do Y", "switch to Z".
14909
+ - Durable rationale that explains WHY an approach was chosen.
14910
+ - Specific feature requirements stated as durable goals.
14911
+
14912
+ # PIVOT vs OBSERVATION test
14913
+
14914
+ A pivot is FUTURE-TENSE and changes the plan: "instead of X, let's do Y", "switch this to Z", "actually, let's not do A".
14915
+ An observation is PAST-TENSE or PRESENT-TENSE and reports state: "we recently did X", "I just noticed Y", "this is broken right now".
14916
+ Observations may frame narrative context but are NOT pivots and NOT durable. Drop them as U: lines.
14917
+
14918
+ # CROSS-COMPARTMENT CHECK (forward-looking)
14919
+
14920
+ Before writing ANY U: line in the current compartment:
14921
+ 1. Scan U: lines you have ALREADY written in previous compartments in this response.
14922
+ 2. If any prior U: line expresses the same intent, decision, constraint, or rationale \u2014 even in different words \u2014 do NOT write the new U: line.
14923
+ 3. Let the narrative in the current compartment carry the signal instead.
14924
+
14925
+ This is a forward operation: you only need to check what you already wrote, not revisit past compartments.
14926
+
14927
+ Examples of same-intent pairs to collapse:
14928
+ - "X shouldn't cause cache bust" + "X must not bust cache by itself" \u2192 keep only the first, in its original compartment.
14929
+ - "Let's use monorepo" + "Yes, monorepo is the right call" \u2192 keep only the first.
14930
+ - "Add logging" + "We need logs here too" \u2192 keep only the first.
14931
+
14932
+ Never keep two U: lines for the same underlying directive across compartments.
14933
+
14934
+ # Budget
14935
+
14936
+ - HARD LIMIT: 3\u20135 U: lines per compartment. 0\u20132 is typical.
14937
+ - If you have more than 5 candidate U: lines in one compartment, that is a signal to split into two compartments at a natural pivot, not to stuff more.
14938
+ - Every U: line must be immediately followed by 1\u20133 sentences describing the outcome, decision, or effect. Never stack two U: lines without intervening outcome text.
14939
+
14940
+ # Example: CORRECT preservation (narrative-first, verbatim U: line)
14941
+
14942
+ <compartment start="50" end="120" title="Built the auth layer">
14943
+ Implemented JWT auth with hard 60-minute exp claim and refresh-token rotation. Chose Bearer tokens over cookies after finding cookie-based auth broke the SPA flow. Added session_expiry config (read-only at runtime). Commits: a3f891, b22c4e.
14944
+ U: We need session expiry capped at 1 hour, no exceptions
14945
+ Hardcoded the 60-minute cap at the JWT-issuer layer so runtime overrides cannot extend it.
14946
+ </compartment>
14947
+
14948
+ Notice: only one U: line, kept verbatim from the user's actual message. The cookie-to-Bearer pivot is narrative because paraphrase captures the signal fully.
14949
+
14950
+ # Example: OVER-PRESERVATION (avoid)
14951
+
14952
+ <compartment start="200" end="350" title="Refactored data layer">
14953
+ U: Okay let's start on the data layer
14954
+ U: What about transactions?
14955
+ U: Yes that approach looks good
14956
+ U: Actually wait, maybe we need write-ahead logging
14957
+ U: I just noticed the previous commit broke a test
14958
+ U: Let's commit and ship it
14959
+ Refactored data layer with WAL mode and connection pooling.
14960
+ </compartment>
14961
+
14962
+ Problems: pacing, question, agreement, observation, pacing again. Only one message carries signal, and even that is narrative-capturable.
14963
+
14964
+ # CORRECT version of the above
14965
+
14966
+ <compartment start="200" end="350" title="Refactored data layer">
14967
+ Refactored data layer to use WAL mode plus connection pooling. Chose WAL over plain connections for concurrent read performance under sustained write load.
14791
14968
  </compartment>
14792
14969
 
14970
+ Zero U: lines. The pivot to WAL is clear in narrative.
14971
+
14793
14972
  Fact rules:
14794
14973
  - Facts are editable state, not append-only notes. Rewrite, normalize, deduplicate, or drop existing facts whenever needed.
14795
14974
  - Before emitting any fact, check all existing facts in the same category for semantic duplicates. If two facts describe the same decision, constraint, or default with different wording, merge them into one canonical statement. Never emit two facts that could be answered by the same question.
@@ -14852,7 +15031,44 @@ More summary text.
14852
15031
  </meta>
14853
15032
  </output>
14854
15033
 
14855
- Omit empty fact categories. Compartments must be ordered, contiguous for the ranges they cover, and non-overlapping.`, USER_OBSERVATIONS_APPENDIX = `
15034
+ Omit empty fact categories. Compartments must be ordered, contiguous for the ranges they cover, and non-overlapping.`, HISTORIAN_EDITOR_SYSTEM_PROMPT = `You are an editor refining a historian draft. The draft was produced by a first-pass historian and may contain noise \u2014 low-signal U: lines, redundant quotes across compartments, and weak preservation decisions.
15035
+
15036
+ Your job is to clean the draft without changing its structure:
15037
+
15038
+ 1. DROP low-signal U: lines:
15039
+ - Questions in any form \u2014 resolved decision goes in narrative only.
15040
+ - Pacing/agreement: "let's go", "yes", "okay", "sounds good", "I agree".
15041
+ - Pasted error output, debugging status, mid-process observations.
15042
+ - Tactical micro-direction: "now look at X", "first check Y".
15043
+
15044
+ 2. DROP cross-compartment duplicates:
15045
+ - Scan U: lines across ALL compartments in the draft.
15046
+ - If two U: lines express the same intent/decision, keep only ONE \u2014 in the compartment where the outcome is actually described.
15047
+
15048
+ 3. STRIP agreement prefixes:
15049
+ - "Yes we should X" \u2192 keep only the directive content, or drop entirely if nothing substantive remains after "Yes".
15050
+
15051
+ 4. PREFER verbatim over paraphrase:
15052
+ - If the draft rephrased a user directive into formal constraint language, restore the user's wording if available.
15053
+ - Do not invent technical specificity (file paths, function names, constants) the user did not state.
15054
+
15055
+ 5. FOLD into narrative when possible:
15056
+ - If a U: line's signal is already captured in the surrounding narrative, drop the U: line.
15057
+ - Narrative should not need the U: line to be understood.
15058
+
15059
+ 6. KEEP as U: lines ONLY:
15060
+ - Hard constraints with concrete values (thresholds, byte sizes, timeouts).
15061
+ - Explicit rejections ("X is wrong because Y", "NOT Z").
15062
+ - Implementation pivots in future-tense ("instead of A, do B").
15063
+ - Source-of-truth corrections.
15064
+
15065
+ Do NOT change:
15066
+ - Compartment titles, ranges, or ordering.
15067
+ - Narrative summary text unless it directly references a U: line you dropped (in which case integrate the signal into the narrative).
15068
+ - Facts \u2014 leave the facts section untouched.
15069
+ - <meta> section \u2014 leave messages_processed and unprocessed_from exactly as the draft has them.
15070
+
15071
+ Output the cleaned version as valid XML matching the original structure. Preserve all XML tags, compartment ranges, meta, and facts.`, USER_OBSERVATIONS_APPENDIX = `
14856
15072
 
14857
15073
  User observation rules (EXPERIMENTAL):
14858
15074
  - After outputting compartments and facts, also output a <user_observations> section.
@@ -14873,17 +15089,17 @@ var init_compartment_prompt = __esm(() => {
14873
15089
  });
14874
15090
 
14875
15091
  // src/shared/opencode-config-dir.ts
14876
- import { homedir as homedir2 } from "os";
14877
- import { join as join3, resolve } from "path";
15092
+ import { homedir as homedir3 } from "os";
15093
+ import { join as join3, resolve as resolve2 } from "path";
14878
15094
  function getCliConfigDir() {
14879
15095
  const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
14880
15096
  if (envConfigDir) {
14881
- return resolve(envConfigDir);
15097
+ return resolve2(envConfigDir);
14882
15098
  }
14883
15099
  if (process.platform === "win32") {
14884
- return join3(homedir2(), ".config", "opencode");
15100
+ return join3(homedir3(), ".config", "opencode");
14885
15101
  }
14886
- return join3(process.env.XDG_CONFIG_HOME || join3(homedir2(), ".config"), "opencode");
15102
+ return join3(process.env.XDG_CONFIG_HOME || join3(homedir3(), ".config"), "opencode");
14887
15103
  }
14888
15104
  function getOpenCodeConfigDir(_options) {
14889
15105
  return getCliConfigDir();
@@ -15090,12 +15306,12 @@ __export(exports_conflict_warning_hook, {
15090
15306
  sendConflictWarning: () => sendConflictWarning,
15091
15307
  cleanupConflictWarnings: () => cleanupConflictWarnings
15092
15308
  });
15093
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
15094
- import { homedir as homedir3, platform } from "os";
15309
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
15310
+ import { homedir as homedir4, platform } from "os";
15095
15311
  import { join as join5 } from "path";
15096
15312
  function getDesktopStatePath() {
15097
15313
  const os2 = platform();
15098
- const home = homedir3();
15314
+ const home = homedir4();
15099
15315
  if (os2 === "darwin") {
15100
15316
  return join5(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
15101
15317
  }
@@ -15111,12 +15327,12 @@ function getDesktopStatePath() {
15111
15327
  }
15112
15328
  function readDesktopState(directory) {
15113
15329
  const statePath = getDesktopStatePath();
15114
- if (!statePath || !existsSync3(statePath)) {
15330
+ if (!statePath || !existsSync4(statePath)) {
15115
15331
  log(`[magic-context] conflict-warning: Desktop state file not found at ${statePath}`);
15116
15332
  return { sessionId: null, sidecarUrl: null };
15117
15333
  }
15118
15334
  try {
15119
- const raw = readFileSync3(statePath, "utf-8");
15335
+ const raw = readFileSync4(statePath, "utf-8");
15120
15336
  const state = JSON.parse(raw);
15121
15337
  let sidecarUrl = null;
15122
15338
  const serverStr = state.server;
@@ -16102,6 +16318,8 @@ var exports_read_session_db = {};
16102
16318
  __export(exports_read_session_db, {
16103
16319
  withReadOnlySessionDb: () => withReadOnlySessionDb,
16104
16320
  getRawSessionMessageCountFromDb: () => getRawSessionMessageCountFromDb,
16321
+ getMessageTimesFromOpenCodeDb: () => getMessageTimesFromOpenCodeDb,
16322
+ findLastAssistantModelFromOpenCodeDb: () => findLastAssistantModelFromOpenCodeDb,
16105
16323
  closeReadOnlySessionDb: () => closeReadOnlySessionDb
16106
16324
  });
16107
16325
  import { Database } from "bun:sqlite";
@@ -16143,6 +16361,47 @@ function getRawSessionMessageCountFromDb(db, sessionId) {
16143
16361
  AND COALESCE(json_extract(data, '$.finish'), '') = 'stop')`).get(sessionId);
16144
16362
  return typeof row?.count === "number" ? row.count : 0;
16145
16363
  }
16364
+ function getMessageTimesFromOpenCodeDb(sessionId, messageIds) {
16365
+ const result = new Map;
16366
+ if (messageIds.length === 0)
16367
+ return result;
16368
+ try {
16369
+ withReadOnlySessionDb((db) => {
16370
+ const placeholders = messageIds.map(() => "?").join(",");
16371
+ const rows = db.prepare(`SELECT id, time_created FROM message WHERE session_id = ? AND id IN (${placeholders})`).all(sessionId, ...messageIds);
16372
+ for (const row of rows) {
16373
+ if (typeof row.id === "string" && typeof row.time_created === "number") {
16374
+ result.set(row.id, row.time_created);
16375
+ }
16376
+ }
16377
+ });
16378
+ } catch (error48) {
16379
+ log("[magic-context] failed to resolve message times from OpenCode DB:", error48);
16380
+ }
16381
+ return result;
16382
+ }
16383
+ function findLastAssistantModelFromOpenCodeDb(sessionId) {
16384
+ try {
16385
+ return withReadOnlySessionDb((db) => {
16386
+ const row = db.prepare(`SELECT json_extract(data, '$.providerID') as providerID,
16387
+ json_extract(data, '$.modelID') as modelID
16388
+ FROM message
16389
+ WHERE session_id = ?
16390
+ AND json_extract(data, '$.role') = 'assistant'
16391
+ AND json_extract(data, '$.providerID') IS NOT NULL
16392
+ AND json_extract(data, '$.modelID') IS NOT NULL
16393
+ ORDER BY time_created DESC
16394
+ LIMIT 1`).get(sessionId);
16395
+ if (!row || typeof row.providerID !== "string" || typeof row.modelID !== "string") {
16396
+ return null;
16397
+ }
16398
+ return { providerID: row.providerID, modelID: row.modelID };
16399
+ });
16400
+ } catch (error48) {
16401
+ log("[magic-context] failed to recover live model from OpenCode DB:", error48);
16402
+ return null;
16403
+ }
16404
+ }
16146
16405
  var cachedReadOnlyDb = null;
16147
16406
  var init_read_session_db = __esm(() => {
16148
16407
  init_data_path();
@@ -16206,7 +16465,7 @@ async function acquireModelLoadLock(lockPath) {
16206
16465
  log("[magic-context] embedding-load lock wait exceeded, proceeding without lock");
16207
16466
  return async () => {};
16208
16467
  }
16209
- await new Promise((resolve2) => setTimeout(resolve2, LOCK_POLL_MS));
16468
+ await new Promise((resolve3) => setTimeout(resolve3, LOCK_POLL_MS));
16210
16469
  }
16211
16470
  }
16212
16471
  }
@@ -16331,7 +16590,7 @@ class LocalEmbeddingProvider {
16331
16590
  }
16332
16591
  const delayMs = 300 * attempt + Math.floor(Math.random() * 200);
16333
16592
  log(`[magic-context] embedding model load attempt ${attempt}/${MAX_ATTEMPTS} failed transiently, retrying in ${delayMs}ms`);
16334
- await new Promise((resolve2) => setTimeout(resolve2, delayMs));
16593
+ await new Promise((resolve3) => setTimeout(resolve3, delayMs));
16335
16594
  }
16336
16595
  }
16337
16596
  if (this.pipeline) {
@@ -16695,14 +16954,6 @@ function getTotalDepthStatement(db) {
16695
16954
  }
16696
16955
  return stmt;
16697
16956
  }
16698
- function getMaxDepthStatement(db) {
16699
- let stmt = maxDepthStatements.get(db);
16700
- if (!stmt) {
16701
- stmt = db.prepare("SELECT COALESCE(MAX(depth), 0) AS max_depth FROM compression_depth WHERE session_id = ?");
16702
- maxDepthStatements.set(db, stmt);
16703
- }
16704
- return stmt;
16705
- }
16706
16957
  function getClearDepthStatement(db) {
16707
16958
  let stmt = clearDepthStatements.get(db);
16708
16959
  if (!stmt) {
@@ -16731,13 +16982,15 @@ function getAverageCompressionDepth(db, sessionId, startOrdinal, endOrdinal) {
16731
16982
  const messageCount = endOrdinal - startOrdinal + 1;
16732
16983
  return totalDepth / messageCount;
16733
16984
  }
16734
- function getMaxCompressionDepth(db, sessionId) {
16735
- const row = getMaxDepthStatement(db).get(sessionId);
16736
- return typeof row?.max_depth === "number" ? row.max_depth : 0;
16737
- }
16738
16985
  function clearCompressionDepth(db, sessionId) {
16739
16986
  getClearDepthStatement(db).run(sessionId);
16740
16987
  }
16988
+ function clearCompressionDepthRange(db, sessionId, startOrdinal, endOrdinal) {
16989
+ if (endOrdinal < startOrdinal) {
16990
+ return;
16991
+ }
16992
+ db.prepare("DELETE FROM compression_depth WHERE session_id = ? AND message_ordinal BETWEEN ? AND ?").run(sessionId, startOrdinal, endOrdinal);
16993
+ }
16741
16994
  var incrementDepthStatements, totalDepthStatements, maxDepthStatements, clearDepthStatements;
16742
16995
  var init_compression_depth_storage = __esm(() => {
16743
16996
  incrementDepthStatements = new WeakMap;
@@ -149529,6 +149782,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
149529
149782
  const startOrdinal = Math.max(1, offset);
149530
149783
  const lines = [];
149531
149784
  const lineMeta = [];
149785
+ const flushedToolOnlyBlocks = [];
149532
149786
  let totalTokens = 0;
149533
149787
  let messagesProcessed = 0;
149534
149788
  let lastOrdinal = startOrdinal - 1;
@@ -149558,6 +149812,12 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
149558
149812
  lines.push(blockText);
149559
149813
  lineMeta.push(...currentBlock.meta);
149560
149814
  totalTokens += blockTokens;
149815
+ if (currentBlock.isToolOnly) {
149816
+ flushedToolOnlyBlocks.push({
149817
+ start: currentBlock.startOrdinal,
149818
+ end: currentBlock.endOrdinal
149819
+ });
149820
+ }
149561
149821
  currentBlock = null;
149562
149822
  return true;
149563
149823
  }
@@ -149588,7 +149848,8 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
149588
149848
  endOrdinal: msg.ordinal,
149589
149849
  parts: [tcText],
149590
149850
  meta: [...pendingNoiseMeta, meta3],
149591
- commitHashes: []
149851
+ commitHashes: [],
149852
+ isToolOnly: true
149592
149853
  };
149593
149854
  pendingNoiseMeta = [];
149594
149855
  }
@@ -149604,11 +149865,14 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
149604
149865
  pendingNoiseMeta.push(meta3);
149605
149866
  continue;
149606
149867
  }
149868
+ const msgHasNarrative = textParts.length > 0;
149607
149869
  if (currentBlock && currentBlock.role === role) {
149608
149870
  currentBlock.endOrdinal = msg.ordinal;
149609
149871
  currentBlock.parts.push(text);
149610
149872
  currentBlock.meta.push(...pendingNoiseMeta, meta3);
149611
149873
  currentBlock.commitHashes = mergeCommitHashes(currentBlock.commitHashes, compacted.commitHashes);
149874
+ if (msgHasNarrative)
149875
+ currentBlock.isToolOnly = false;
149612
149876
  pendingNoiseMeta = [];
149613
149877
  continue;
149614
149878
  }
@@ -149620,11 +149884,21 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
149620
149884
  endOrdinal: msg.ordinal,
149621
149885
  parts: [text],
149622
149886
  meta: [...pendingNoiseMeta, meta3],
149623
- commitHashes: [...compacted.commitHashes]
149887
+ commitHashes: [...compacted.commitHashes],
149888
+ isToolOnly: !msgHasNarrative
149624
149889
  };
149625
149890
  pendingNoiseMeta = [];
149626
149891
  }
149627
149892
  flushCurrentBlock();
149893
+ const toolOnlyRanges = [];
149894
+ for (const range of flushedToolOnlyBlocks) {
149895
+ const last = toolOnlyRanges[toolOnlyRanges.length - 1];
149896
+ if (last && range.start === last.end + 1) {
149897
+ last.end = range.end;
149898
+ } else {
149899
+ toolOnlyRanges.push({ start: range.start, end: range.end });
149900
+ }
149901
+ }
149628
149902
  return {
149629
149903
  startIndex: startOrdinal,
149630
149904
  endIndex: lastOrdinal,
@@ -149636,7 +149910,8 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
149636
149910
  text: lines.join(`
149637
149911
  `),
149638
149912
  lines: lineMeta,
149639
- commitClusterCount: commitClusters
149913
+ commitClusterCount: commitClusters,
149914
+ toolOnlyRanges
149640
149915
  };
149641
149916
  }
149642
149917
  var activeRawMessageCache = null, PROTECTED_TAIL_USER_TURNS = 5;
@@ -150214,6 +150489,8 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
150214
150489
  ensureColumn(db, "session_meta", "key_files", "TEXT DEFAULT ''");
150215
150490
  ensureColumn(db, "session_meta", "conversation_tokens", "INTEGER DEFAULT 0");
150216
150491
  ensureColumn(db, "session_meta", "tool_call_tokens", "INTEGER DEFAULT 0");
150492
+ ensureColumn(db, "session_meta", "recomp_partial_range_start", "INTEGER DEFAULT 0");
150493
+ ensureColumn(db, "session_meta", "recomp_partial_range_end", "INTEGER DEFAULT 0");
150217
150494
  healNullTextColumns(db);
150218
150495
  healNullIntegerColumns(db);
150219
150496
  }
@@ -150953,8 +151230,8 @@ var init_storage = __esm(() => {
150953
151230
 
150954
151231
  // src/shared/models-dev-cache.ts
150955
151232
  import { createHash } from "crypto";
150956
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
150957
- import { homedir as homedir5, platform as platform2 } from "os";
151233
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
151234
+ import { homedir as homedir6, platform as platform2 } from "os";
150958
151235
  import { join as join11 } from "path";
150959
151236
  function hashFast(input) {
150960
151237
  return createHash("sha1").update(input).digest("hex");
@@ -150969,9 +151246,9 @@ function getModelsJsonPath() {
150969
151246
  if (xdgCache) {
150970
151247
  cacheBase = xdgCache;
150971
151248
  } else if (os3 === "win32") {
150972
- cacheBase = process.env.LOCALAPPDATA ?? join11(homedir5(), "AppData", "Local");
151249
+ cacheBase = process.env.LOCALAPPDATA ?? join11(homedir6(), "AppData", "Local");
150973
151250
  } else {
150974
- cacheBase = join11(homedir5(), ".cache");
151251
+ cacheBase = join11(homedir6(), ".cache");
150975
151252
  }
150976
151253
  const source = process.env.OPENCODE_MODELS_URL?.trim();
150977
151254
  const filename = source && source !== "https://models.dev" ? `models-${hashFast(source)}.json` : "models.json";
@@ -150979,12 +151256,12 @@ function getModelsJsonPath() {
150979
151256
  }
150980
151257
  function getOpencodeConfigPath() {
150981
151258
  const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
150982
- const configDir = envDir ? envDir : platform2() === "win32" ? join11(homedir5(), ".config", "opencode") : join11(process.env.XDG_CONFIG_HOME || join11(homedir5(), ".config"), "opencode");
151259
+ const configDir = envDir ? envDir : platform2() === "win32" ? join11(homedir6(), ".config", "opencode") : join11(process.env.XDG_CONFIG_HOME || join11(homedir6(), ".config"), "opencode");
150983
151260
  const jsonc = join11(configDir, "opencode.jsonc");
150984
- if (existsSync5(jsonc))
151261
+ if (existsSync6(jsonc))
150985
151262
  return jsonc;
150986
151263
  const json2 = join11(configDir, "opencode.json");
150987
- if (existsSync5(json2))
151264
+ if (existsSync6(json2))
150988
151265
  return json2;
150989
151266
  return null;
150990
151267
  }
@@ -151002,9 +151279,9 @@ function loadModelsDevLimitsFromFile() {
151002
151279
  const modelsJsonPath = getModelsJsonPath();
151003
151280
  let fileFound = false;
151004
151281
  try {
151005
- if (existsSync5(modelsJsonPath)) {
151282
+ if (existsSync6(modelsJsonPath)) {
151006
151283
  fileFound = true;
151007
- const raw = readFileSync4(modelsJsonPath, "utf-8");
151284
+ const raw = readFileSync5(modelsJsonPath, "utf-8");
151008
151285
  const data = JSON.parse(raw);
151009
151286
  for (const [providerId, provider2] of Object.entries(data)) {
151010
151287
  if (!provider2?.models || typeof provider2.models !== "object")
@@ -151028,8 +151305,8 @@ function loadModelsDevLimitsFromFile() {
151028
151305
  }
151029
151306
  try {
151030
151307
  const configPath = getOpencodeConfigPath();
151031
- if (configPath && existsSync5(configPath)) {
151032
- let raw = readFileSync4(configPath, "utf-8");
151308
+ if (configPath && existsSync6(configPath)) {
151309
+ let raw = readFileSync5(configPath, "utf-8");
151033
151310
  raw = raw.replace(/"(?:[^"\\]|\\.)*"|\/\/.*$/gm, (match) => match.startsWith('"') ? match : "");
151034
151311
  const config2 = JSON.parse(raw);
151035
151312
  if (config2.provider && typeof config2.provider === "object") {
@@ -151072,9 +151349,12 @@ async function refreshModelLimitsFromApi(client) {
151072
151349
  }
151073
151350
  }
151074
151351
  }
151352
+ const previousSize = apiCache?.size ?? null;
151075
151353
  apiCache = map2;
151076
151354
  apiLoadedAt = Date.now();
151077
- sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model limits`);
151355
+ if (previousSize === null || previousSize !== map2.size) {
151356
+ sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model limits${previousSize !== null ? ` (was ${previousSize})` : ""}`);
151357
+ }
151078
151358
  } catch (error48) {
151079
151359
  sessionLog("global", "models-dev-cache: API refresh failed:", error48 instanceof Error ? error48.message : String(error48));
151080
151360
  }
@@ -151165,608 +151445,6 @@ var init_rpc_notifications = __esm(() => {
151165
151445
  queue2 = [];
151166
151446
  });
151167
151447
 
151168
- // src/hooks/magic-context/send-session-notification.ts
151169
- var exports_send_session_notification = {};
151170
- __export(exports_send_session_notification, {
151171
- sendUserPrompt: () => sendUserPrompt,
151172
- sendIgnoredMessage: () => sendIgnoredMessage
151173
- });
151174
- function hasNotificationSessionClient(client) {
151175
- if (client === null || typeof client !== "object")
151176
- return false;
151177
- const candidate = client;
151178
- if (candidate.session === undefined)
151179
- return true;
151180
- if (candidate.session === null || typeof candidate.session !== "object")
151181
- return false;
151182
- const session = candidate.session;
151183
- return (session.prompt === undefined || typeof session.prompt === "function") && (session.promptAsync === undefined || typeof session.promptAsync === "function");
151184
- }
151185
- function inferToastVariant(text) {
151186
- const lower = text.toLowerCase();
151187
- if (lower.includes("error") || lower.includes("failed") || lower.includes("alert"))
151188
- return "error";
151189
- if (lower.includes("warning") || lower.includes("\u26A0"))
151190
- return "warning";
151191
- if (lower.includes("complete") || lower.includes("success") || lower.includes("\u2713") || lower.includes("finished"))
151192
- return "success";
151193
- return "info";
151194
- }
151195
- function extractToastTitle(text) {
151196
- const headingMatch = text.match(/^#+\s+(.+)/m);
151197
- if (headingMatch)
151198
- return headingMatch[1].trim();
151199
- const firstLine = text.split(`
151200
- `)[0].trim();
151201
- if (firstLine.length <= 80)
151202
- return firstLine;
151203
- return "Magic Context";
151204
- }
151205
- async function sendIgnoredMessage(client, sessionId, text, params) {
151206
- const { isTuiConnected: checkTui } = await Promise.resolve().then(() => (init_rpc_notifications(), exports_rpc_notifications));
151207
- if (checkTui()) {
151208
- try {
151209
- const c2 = client;
151210
- const tui = c2?.tui;
151211
- if (typeof tui?.showToast === "function") {
151212
- const tuiClient = tui;
151213
- await tuiClient.showToast({
151214
- body: {
151215
- title: extractToastTitle(text),
151216
- message: text.length > 200 ? `${text.slice(0, 200)}\u2026` : text,
151217
- variant: inferToastVariant(text),
151218
- duration: 5000
151219
- }
151220
- }).catch(() => {});
151221
- return;
151222
- }
151223
- } catch {}
151224
- }
151225
- const agent = params.agent || undefined;
151226
- const variant = params.variant || undefined;
151227
- const model = params.providerId && params.modelId ? {
151228
- providerID: params.providerId,
151229
- modelID: params.modelId
151230
- } : undefined;
151231
- if (!hasNotificationSessionClient(client)) {
151232
- sessionLog(sessionId, "session prompt API unavailable for notification");
151233
- return;
151234
- }
151235
- const c = client;
151236
- const input = {
151237
- path: { id: sessionId },
151238
- body: {
151239
- noReply: true,
151240
- agent,
151241
- model,
151242
- variant,
151243
- parts: [
151244
- {
151245
- type: "text",
151246
- text,
151247
- ignored: true
151248
- }
151249
- ]
151250
- }
151251
- };
151252
- try {
151253
- if (typeof c.session?.prompt === "function") {
151254
- await Promise.resolve(c.session.prompt(input));
151255
- } else if (typeof c.session?.promptAsync === "function") {
151256
- await c.session.promptAsync(input);
151257
- } else {
151258
- sessionLog(sessionId, "session prompt API unavailable for notification");
151259
- }
151260
- } catch (error48) {
151261
- const msg = getErrorMessage(error48);
151262
- sessionLog(sessionId, "failed to send notification:", msg);
151263
- }
151264
- }
151265
- async function sendUserPrompt(client, sessionId, text) {
151266
- if (!hasNotificationSessionClient(client)) {
151267
- sessionLog(sessionId, "session prompt API unavailable for user prompt");
151268
- return;
151269
- }
151270
- const c = client;
151271
- const input = {
151272
- path: { id: sessionId },
151273
- body: {
151274
- parts: [{ type: "text", text }]
151275
- }
151276
- };
151277
- try {
151278
- if (typeof c.session?.promptAsync === "function") {
151279
- await c.session.promptAsync(input);
151280
- } else if (typeof c.session?.prompt === "function") {
151281
- await Promise.resolve(c.session.prompt(input));
151282
- } else {
151283
- sessionLog(sessionId, "session prompt API unavailable for user prompt");
151284
- }
151285
- } catch (error48) {
151286
- const msg = getErrorMessage(error48);
151287
- sessionLog(sessionId, "failed to send user prompt:", msg);
151288
- }
151289
- }
151290
- var init_send_session_notification = __esm(() => {
151291
- init_logger();
151292
- });
151293
-
151294
- // src/hooks/magic-context/derive-budgets.ts
151295
- var exports_derive_budgets = {};
151296
- __export(exports_derive_budgets, {
151297
- resolveHistorianContextLimit: () => resolveHistorianContextLimit,
151298
- deriveTriggerBudget: () => deriveTriggerBudget,
151299
- deriveHistorianChunkTokens: () => deriveHistorianChunkTokens
151300
- });
151301
- function deriveTriggerBudget(mainContextLimit, executeThresholdPercentage) {
151302
- if (!Number.isFinite(mainContextLimit) || mainContextLimit <= 0) {
151303
- return TRIGGER_BUDGET_MIN;
151304
- }
151305
- const thresholdFraction = Math.max(0, executeThresholdPercentage) / 100;
151306
- const usable = mainContextLimit * thresholdFraction;
151307
- const derived = Math.round(usable * TRIGGER_BUDGET_PERCENTAGE);
151308
- return Math.max(TRIGGER_BUDGET_MIN, Math.min(TRIGGER_BUDGET_MAX, derived));
151309
- }
151310
- function deriveHistorianChunkTokens(historianContextLimit) {
151311
- if (!Number.isFinite(historianContextLimit) || historianContextLimit <= 0) {
151312
- return HISTORIAN_CHUNK_MIN;
151313
- }
151314
- const derived = Math.round(historianContextLimit * HISTORIAN_CHUNK_PERCENTAGE);
151315
- return Math.max(HISTORIAN_CHUNK_MIN, Math.min(HISTORIAN_CHUNK_MAX, derived));
151316
- }
151317
- function resolveHistorianContextLimit(historianModelOverride) {
151318
- if (typeof historianModelOverride === "string" && historianModelOverride.includes("/")) {
151319
- const [providerID, ...rest] = historianModelOverride.split("/");
151320
- const modelID = rest.join("/");
151321
- if (providerID && modelID) {
151322
- const limit = getModelsDevContextLimit(providerID, modelID);
151323
- if (typeof limit === "number" && limit > 0)
151324
- return limit;
151325
- }
151326
- return DEFAULT_HISTORIAN_CONTEXT_FALLBACK;
151327
- }
151328
- if (typeof historianModelOverride === "string" && historianModelOverride.trim() !== "") {
151329
- console.warn(`[magic-context] historian.model "${historianModelOverride}" lacks provider prefix ("provider/model-id"); using fallback chain for chunk-budget derivation.`);
151330
- }
151331
- const chain = AGENT_MODEL_REQUIREMENTS[HISTORIAN_AGENT]?.fallbackChain;
151332
- if (!chain || chain.length === 0)
151333
- return DEFAULT_HISTORIAN_CONTEXT_FALLBACK;
151334
- const expanded = expandFallbackChain(chain);
151335
- let minLimit;
151336
- for (const key of expanded) {
151337
- const [providerID, ...rest] = key.split("/");
151338
- const modelID = rest.join("/");
151339
- if (!providerID || !modelID)
151340
- continue;
151341
- const limit = getModelsDevContextLimit(providerID, modelID);
151342
- if (typeof limit !== "number" || limit <= 0)
151343
- continue;
151344
- if (minLimit === undefined || limit < minLimit)
151345
- minLimit = limit;
151346
- }
151347
- return minLimit ?? DEFAULT_HISTORIAN_CONTEXT_FALLBACK;
151348
- }
151349
- var TRIGGER_BUDGET_PERCENTAGE = 0.05, TRIGGER_BUDGET_MIN = 5000, TRIGGER_BUDGET_MAX = 50000, HISTORIAN_CHUNK_PERCENTAGE = 0.25, HISTORIAN_CHUNK_MIN = 8000, HISTORIAN_CHUNK_MAX = 50000, DEFAULT_HISTORIAN_CONTEXT_FALLBACK = 128000;
151350
- var init_derive_budgets = __esm(() => {
151351
- init_model_requirements();
151352
- init_models_dev_cache();
151353
- });
151354
-
151355
- // src/features/magic-context/compaction-marker.ts
151356
- import { Database as Database4 } from "bun:sqlite";
151357
- import { join as join12 } from "path";
151358
- function randomBase62(length) {
151359
- const chars = [];
151360
- for (let i = 0;i < length; i++) {
151361
- chars.push(BASE62_CHARS[Math.floor(Math.random() * BASE62_CHARS.length)]);
151362
- }
151363
- return chars.join("");
151364
- }
151365
- function generateId(prefix, timestampMs, counter = 0n) {
151366
- const encoded = BigInt(timestampMs) * 0x1000n + counter;
151367
- const hex3 = encoded.toString(16).padStart(14, "0");
151368
- return `${prefix}_${hex3}${randomBase62(14)}`;
151369
- }
151370
- function generateMessageId(timestampMs, counter = 0n) {
151371
- return generateId("msg", timestampMs, counter);
151372
- }
151373
- function generatePartId(timestampMs, counter = 0n) {
151374
- return generateId("prt", timestampMs, counter);
151375
- }
151376
- function getOpenCodeDbPath3() {
151377
- return join12(getDataDir(), "opencode", "opencode.db");
151378
- }
151379
- function getWritableOpenCodeDb() {
151380
- const dbPath = getOpenCodeDbPath3();
151381
- if (cachedWriteDb?.path === dbPath) {
151382
- return cachedWriteDb.db;
151383
- }
151384
- if (cachedWriteDb) {
151385
- try {
151386
- cachedWriteDb.db.close(false);
151387
- } catch {}
151388
- }
151389
- const db = new Database4(dbPath);
151390
- db.exec("PRAGMA journal_mode=WAL");
151391
- db.exec("PRAGMA busy_timeout=5000");
151392
- cachedWriteDb = { path: dbPath, db };
151393
- return db;
151394
- }
151395
- function findBoundaryUserMessage(sessionId, endOrdinal) {
151396
- const db = getWritableOpenCodeDb();
151397
- const rows = db.prepare("SELECT id, time_created, data FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId);
151398
- const filtered = rows.filter((row) => {
151399
- try {
151400
- const info = JSON.parse(row.data);
151401
- return !(info.summary === true && info.finish === "stop");
151402
- } catch {
151403
- return true;
151404
- }
151405
- });
151406
- let bestMatch = null;
151407
- for (let i = 0;i < filtered.length && i < endOrdinal; i++) {
151408
- const row = filtered[i];
151409
- try {
151410
- const info = JSON.parse(row.data);
151411
- if (info.role === "user") {
151412
- bestMatch = { id: row.id, timeCreated: row.time_created };
151413
- }
151414
- } catch {}
151415
- }
151416
- return bestMatch;
151417
- }
151418
- function injectCompactionMarker(args) {
151419
- const boundary = findBoundaryUserMessage(args.sessionId, args.endOrdinal);
151420
- if (!boundary) {
151421
- log(`[magic-context] compaction-marker: no user message found at or before ordinal ${args.endOrdinal}`);
151422
- return null;
151423
- }
151424
- const db = getWritableOpenCodeDb();
151425
- const boundaryTime = boundary.timeCreated;
151426
- const summaryMsgId = generateMessageId(boundaryTime + 1, 1n);
151427
- const compactionPartId = generatePartId(boundaryTime, 1n);
151428
- const summaryPartId = generatePartId(boundaryTime + 1, 2n);
151429
- const summaryMsgData = JSON.stringify({
151430
- role: "assistant",
151431
- parentID: boundary.id,
151432
- summary: true,
151433
- finish: "stop",
151434
- mode: "compaction",
151435
- agent: "compaction",
151436
- path: { cwd: args.directory, root: args.directory },
151437
- cost: 0,
151438
- tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
151439
- modelID: "magic-context",
151440
- providerID: "magic-context",
151441
- time: { created: boundaryTime + 1 }
151442
- });
151443
- try {
151444
- db.transaction(() => {
151445
- db.prepare("INSERT INTO part (id, message_id, session_id, time_created, time_updated, data) VALUES (?, ?, ?, ?, ?, ?)").run(compactionPartId, boundary.id, args.sessionId, boundaryTime, boundaryTime, '{"type":"compaction","auto":true}');
151446
- db.prepare("INSERT INTO message (id, session_id, time_created, time_updated, data) VALUES (?, ?, ?, ?, ?)").run(summaryMsgId, args.sessionId, boundaryTime + 1, boundaryTime + 1, summaryMsgData);
151447
- db.prepare("INSERT INTO part (id, message_id, session_id, time_created, time_updated, data) VALUES (?, ?, ?, ?, ?, ?)").run(summaryPartId, summaryMsgId, args.sessionId, boundaryTime + 1, boundaryTime + 1, JSON.stringify({ type: "text", text: args.summaryText }));
151448
- })();
151449
- log(`[magic-context] compaction-marker: injected boundary at user msg ${boundary.id} (ordinal ~${args.endOrdinal}), summary msg ${summaryMsgId}`);
151450
- return {
151451
- boundaryMessageId: boundary.id,
151452
- summaryMessageId: summaryMsgId,
151453
- compactionPartId,
151454
- summaryPartId
151455
- };
151456
- } catch (error48) {
151457
- log(`[magic-context] compaction-marker: injection failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
151458
- return null;
151459
- }
151460
- }
151461
- function removeCompactionMarker(state) {
151462
- try {
151463
- const db = getWritableOpenCodeDb();
151464
- db.transaction(() => {
151465
- db.prepare("DELETE FROM part WHERE id = ?").run(state.summaryPartId);
151466
- db.prepare("DELETE FROM message WHERE id = ?").run(state.summaryMessageId);
151467
- db.prepare("DELETE FROM part WHERE id = ?").run(state.compactionPartId);
151468
- })();
151469
- return true;
151470
- } catch (error48) {
151471
- log(`[magic-context] compaction-marker: removal failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
151472
- return false;
151473
- }
151474
- }
151475
- var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", cachedWriteDb = null;
151476
- var init_compaction_marker = __esm(() => {
151477
- init_data_path();
151478
- init_logger();
151479
- });
151480
-
151481
- // src/hooks/magic-context/compaction-marker-manager.ts
151482
- function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, directory) {
151483
- const existing = getPersistedCompactionMarkerState(db, sessionId);
151484
- if (existing) {
151485
- if (existing.boundaryOrdinal === lastCompartmentEnd) {
151486
- return;
151487
- }
151488
- try {
151489
- removeCompactionMarker(existing);
151490
- setPersistedCompactionMarkerState(db, sessionId, null);
151491
- sessionLog(sessionId, `compaction-marker: removed old boundary at ordinal ${existing.boundaryOrdinal}, moving to ${lastCompartmentEnd}`);
151492
- } catch (error48) {
151493
- sessionLog(sessionId, `compaction-marker: failed to remove old boundary at ordinal ${existing.boundaryOrdinal}, proceeding with new injection:`, error48);
151494
- }
151495
- }
151496
- const result = injectCompactionMarker({
151497
- sessionId,
151498
- endOrdinal: lastCompartmentEnd,
151499
- summaryText: MARKER_SUMMARY_TEXT,
151500
- directory: directory ?? process.cwd()
151501
- });
151502
- if (result) {
151503
- setPersistedCompactionMarkerState(db, sessionId, {
151504
- ...result,
151505
- boundaryOrdinal: lastCompartmentEnd
151506
- });
151507
- sessionLog(sessionId, `compaction-marker: injected at ordinal ${lastCompartmentEnd}, boundary user msg ${result.boundaryMessageId}`);
151508
- }
151509
- }
151510
- function removeCompactionMarkerForSession(db, sessionId) {
151511
- const existing = getPersistedCompactionMarkerState(db, sessionId);
151512
- if (existing) {
151513
- try {
151514
- removeCompactionMarker(existing);
151515
- setPersistedCompactionMarkerState(db, sessionId, null);
151516
- sessionLog(sessionId, "compaction-marker: removed on session cleanup");
151517
- } catch (error48) {
151518
- setPersistedCompactionMarkerState(db, sessionId, null);
151519
- sessionLog(sessionId, "compaction-marker: removal failed during session cleanup, cleared persisted state:", error48);
151520
- }
151521
- }
151522
- }
151523
- var MARKER_SUMMARY_TEXT = "[Compacted by magic-context \u2014 session history is managed by the plugin]";
151524
- var init_compaction_marker_manager = __esm(() => {
151525
- init_compaction_marker();
151526
- init_storage_meta_persisted();
151527
- init_logger();
151528
- });
151529
-
151530
- // src/hooks/magic-context/note-nudger.ts
151531
- function getPersistedNoteNudgeDeliveredAt(_db, sessionId) {
151532
- return lastDeliveredAt.get(sessionId) ?? 0;
151533
- }
151534
- function recordNoteNudgeDeliveryTime(sessionId) {
151535
- lastDeliveredAt.set(sessionId, Date.now());
151536
- }
151537
- function onNoteTrigger(db, sessionId, trigger) {
151538
- setPersistedNoteNudgeTrigger(db, sessionId);
151539
- sessionLog(sessionId, `note-nudge: trigger fired (${trigger}), triggerPending=true`);
151540
- }
151541
- function peekNoteNudgeText(db, sessionId, currentUserMessageId, projectIdentity) {
151542
- const state = getPersistedNoteNudge(db, sessionId);
151543
- if (!state.triggerPending)
151544
- return null;
151545
- if (!state.triggerMessageId && currentUserMessageId) {
151546
- setPersistedNoteNudgeTriggerMessageId(db, sessionId, currentUserMessageId);
151547
- state.triggerMessageId = currentUserMessageId;
151548
- }
151549
- if (state.triggerMessageId && currentUserMessageId && state.triggerMessageId === currentUserMessageId) {
151550
- sessionLog(sessionId, `note-nudge: deferring \u2014 current user message ${currentUserMessageId} is same as trigger-time message`);
151551
- return null;
151552
- }
151553
- const deliveredAt = getPersistedNoteNudgeDeliveredAt(db, sessionId);
151554
- if (deliveredAt > 0 && Date.now() - deliveredAt < NOTE_NUDGE_COOLDOWN_MS) {
151555
- sessionLog(sessionId, `note-nudge: suppressing \u2014 last delivered ${Math.round((Date.now() - deliveredAt) / 1000)}s ago (cooldown ${NOTE_NUDGE_COOLDOWN_MS / 60000}m)`);
151556
- clearPersistedNoteNudge(db, sessionId);
151557
- return null;
151558
- }
151559
- const notes = getSessionNotes(db, sessionId);
151560
- const readySmartCount = projectIdentity ? getReadySmartNotes(db, projectIdentity).length : 0;
151561
- const totalCount = notes.length + readySmartCount;
151562
- if (totalCount === 0) {
151563
- sessionLog(sessionId, "note-nudge: triggerPending but no notes found, skipping");
151564
- clearPersistedNoteNudge(db, sessionId);
151565
- return null;
151566
- }
151567
- const parts = [];
151568
- if (notes.length > 0) {
151569
- parts.push(`${notes.length} deferred note${notes.length === 1 ? "" : "s"}`);
151570
- }
151571
- if (readySmartCount > 0) {
151572
- parts.push(`${readySmartCount} ready smart note${readySmartCount === 1 ? "" : "s"}`);
151573
- }
151574
- sessionLog(sessionId, `note-nudge: delivering nudge for ${parts.join(" and ")}`);
151575
- return `You have ${parts.join(" and ")}. Review with ctx_note read \u2014 some may be actionable now.`;
151576
- }
151577
- function markNoteNudgeDelivered(db, sessionId, text, messageId) {
151578
- setPersistedDeliveredNoteNudge(db, sessionId, messageId ? text : "", messageId ?? "");
151579
- recordNoteNudgeDeliveryTime(sessionId);
151580
- sessionLog(sessionId, messageId ? `note-nudge: marked delivered, sticky anchor=${messageId}` : "note-nudge: marked delivered without anchor");
151581
- }
151582
- function getStickyNoteNudge(db, sessionId) {
151583
- const state = getPersistedNoteNudge(db, sessionId);
151584
- if (!state.stickyText || !state.stickyMessageId)
151585
- return null;
151586
- return { text: state.stickyText, messageId: state.stickyMessageId };
151587
- }
151588
- function clearNoteNudgeState(db, sessionId, options) {
151589
- if (options?.persist !== false) {
151590
- clearPersistedNoteNudge(db, sessionId);
151591
- }
151592
- lastDeliveredAt.delete(sessionId);
151593
- }
151594
- var NOTE_NUDGE_COOLDOWN_MS, lastDeliveredAt;
151595
- var init_note_nudger = __esm(() => {
151596
- init_storage_meta_persisted();
151597
- init_storage_notes();
151598
- init_logger();
151599
- NOTE_NUDGE_COOLDOWN_MS = 15 * 60 * 1000;
151600
- lastDeliveredAt = new Map;
151601
- });
151602
-
151603
- // src/features/magic-context/memory/constants.ts
151604
- var PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, CATEGORY_DEFAULT_TTL;
151605
- var init_constants = __esm(() => {
151606
- PROMOTABLE_CATEGORIES = [
151607
- "ARCHITECTURE_DECISIONS",
151608
- "CONSTRAINTS",
151609
- "CONFIG_DEFAULTS",
151610
- "NAMING",
151611
- "USER_PREFERENCES",
151612
- "USER_DIRECTIVES",
151613
- "ENVIRONMENT",
151614
- "WORKFLOW_RULES",
151615
- "KNOWN_ISSUES"
151616
- ];
151617
- CATEGORY_PRIORITY = [
151618
- "USER_DIRECTIVES",
151619
- "USER_PREFERENCES",
151620
- "NAMING",
151621
- "CONFIG_DEFAULTS",
151622
- "CONSTRAINTS",
151623
- "ARCHITECTURE_DECISIONS",
151624
- "ENVIRONMENT",
151625
- "WORKFLOW_RULES",
151626
- "KNOWN_ISSUES"
151627
- ];
151628
- CATEGORY_DEFAULT_TTL = {
151629
- WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
151630
- KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
151631
- };
151632
- });
151633
-
151634
- // src/features/magic-context/memory/embedding-backfill.ts
151635
- async function ensureMemoryEmbeddings(args) {
151636
- if (!isEmbeddingEnabled()) {
151637
- return args.existingEmbeddings;
151638
- }
151639
- const missingMemories = args.memories.filter((memory) => !args.existingEmbeddings.has(memory.id));
151640
- if (missingMemories.length === 0) {
151641
- return args.existingEmbeddings;
151642
- }
151643
- try {
151644
- const embeddings = await embedBatch(missingMemories.map((memory) => memory.content));
151645
- const modelId = getEmbeddingModelId();
151646
- const staged = new Map;
151647
- args.db.transaction(() => {
151648
- for (const [index, memory] of missingMemories.entries()) {
151649
- const embedding = embeddings[index];
151650
- if (!embedding) {
151651
- continue;
151652
- }
151653
- saveEmbedding(args.db, memory.id, embedding, modelId);
151654
- staged.set(memory.id, embedding);
151655
- }
151656
- })();
151657
- for (const [id, embedding] of staged) {
151658
- args.existingEmbeddings.set(id, embedding);
151659
- }
151660
- } catch (error48) {
151661
- log("[magic-context] failed to backfill memory embeddings:", error48);
151662
- }
151663
- return args.existingEmbeddings;
151664
- }
151665
- var init_embedding_backfill = __esm(() => {
151666
- init_logger();
151667
- init_embedding();
151668
- init_storage_memory_embeddings();
151669
- });
151670
-
151671
- // src/features/magic-context/memory/promotion.ts
151672
- function isPromotableCategory(category) {
151673
- return PROMOTABLE_CATEGORIES.some((promotableCategory) => promotableCategory === category);
151674
- }
151675
- function resolveExpiresAt(category) {
151676
- const ttl = CATEGORY_DEFAULT_TTL[category];
151677
- return ttl === undefined ? null : Date.now() + ttl;
151678
- }
151679
- function promoteSessionFactsToMemory(db, sessionId, projectPath, facts) {
151680
- for (const fact of facts) {
151681
- if (!isPromotableCategory(fact.category)) {
151682
- continue;
151683
- }
151684
- try {
151685
- const normalizedHash = computeNormalizedHash(fact.content);
151686
- const existingMemory = getMemoryByHash(db, projectPath, fact.category, normalizedHash);
151687
- if (existingMemory) {
151688
- updateMemorySeenCount(db, existingMemory.id);
151689
- continue;
151690
- }
151691
- const memoryInput = {
151692
- projectPath,
151693
- category: fact.category,
151694
- content: fact.content,
151695
- sourceSessionId: sessionId,
151696
- sourceType: "historian",
151697
- expiresAt: resolveExpiresAt(fact.category)
151698
- };
151699
- const memory = insertMemory(db, memoryInput);
151700
- embedAndStoreMemory(db, sessionId, memory.id, memory.content);
151701
- } catch (error48) {
151702
- sessionLog(sessionId, `memory promotion failed for fact "${fact.content.slice(0, 60)}":`, error48);
151703
- }
151704
- }
151705
- }
151706
- async function embedAndStoreMemory(db, sessionId, memoryId, content) {
151707
- try {
151708
- const embedding = await embedText(content);
151709
- if (embedding) {
151710
- saveEmbedding(db, memoryId, embedding, getEmbeddingModelId());
151711
- }
151712
- } catch (error48) {
151713
- sessionLog(sessionId, `memory embedding failed for memory ${memoryId}:`, error48);
151714
- }
151715
- }
151716
- var init_promotion = __esm(() => {
151717
- init_logger();
151718
- init_constants();
151719
- init_embedding();
151720
- init_storage_memory();
151721
- init_storage_memory_embeddings();
151722
- });
151723
-
151724
- // src/features/magic-context/memory/storage-memory-fts.ts
151725
- function getSearchStatement(db) {
151726
- let stmt = searchStatements.get(db);
151727
- if (!stmt) {
151728
- const selectColumns = Object.entries(COLUMN_MAP).map(([property, column]) => `memories.${column} AS ${property}`).join(", ");
151729
- stmt = db.prepare(`SELECT ${selectColumns} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path = ? AND memories.status IN ('active', 'permanent') AND (memories.expires_at IS NULL OR memories.expires_at > ?) AND memories_fts MATCH ? ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`);
151730
- searchStatements.set(db, stmt);
151731
- }
151732
- return stmt;
151733
- }
151734
- function sanitizeFtsQuery(query) {
151735
- const tokens = query.split(/\s+/).filter((token) => token.length > 0);
151736
- if (tokens.length === 0)
151737
- return "";
151738
- return tokens.map((token) => `"${token.replace(/"/g, '""')}"`).join(" ");
151739
- }
151740
- function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT) {
151741
- const trimmedQuery = query.trim();
151742
- if (trimmedQuery.length === 0 || limit <= 0) {
151743
- return [];
151744
- }
151745
- const sanitized = sanitizeFtsQuery(trimmedQuery);
151746
- if (sanitized.length === 0) {
151747
- return [];
151748
- }
151749
- const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
151750
- return rows.map(toMemory);
151751
- }
151752
- var DEFAULT_SEARCH_LIMIT = 10, searchStatements;
151753
- var init_storage_memory_fts = __esm(() => {
151754
- init_storage_memory();
151755
- searchStatements = new WeakMap;
151756
- });
151757
- // src/features/magic-context/memory/index.ts
151758
- var init_memory = __esm(() => {
151759
- init_project_identity();
151760
- init_promotion();
151761
- init_constants();
151762
- init_embedding();
151763
- init_embedding_backfill();
151764
- init_embedding_cache();
151765
- init_storage_memory();
151766
- init_storage_memory_embeddings();
151767
- init_storage_memory_fts();
151768
- });
151769
-
151770
151448
  // src/hooks/magic-context/compartment-parser.ts
151771
151449
  function parseCompartmentOutput(text) {
151772
151450
  const compartments = [];
@@ -151817,290 +151495,6 @@ var init_compartment_parser = __esm(() => {
151817
151495
  USER_OBS_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
151818
151496
  });
151819
151497
 
151820
- // src/hooks/magic-context/compartment-runner-compressor.ts
151821
- async function runCompressionPassIfNeeded(deps) {
151822
- const { db, sessionId, historyBudgetTokens } = deps;
151823
- const compartments = getCompartments(db, sessionId);
151824
- if (compartments.length <= 1)
151825
- return false;
151826
- const facts = getSessionFacts(db, sessionId);
151827
- let totalTokens = 0;
151828
- for (const c of compartments) {
151829
- totalTokens += estimateTokens(`<compartment start="${c.startMessage}" end="${c.endMessage}" title="${c.title}">
151830
- ${c.content}
151831
- </compartment>
151832
- `);
151833
- }
151834
- for (const f of facts) {
151835
- totalTokens += estimateTokens(`* ${f.content}
151836
- `);
151837
- }
151838
- if (totalTokens <= historyBudgetTokens) {
151839
- sessionLog(sessionId, `compressor: history block ~${totalTokens} tokens within budget ${historyBudgetTokens}, skipping`);
151840
- return false;
151841
- }
151842
- const overage = totalTokens - historyBudgetTokens;
151843
- sessionLog(sessionId, `compressor: history block ~${totalTokens} tokens exceeds budget ${historyBudgetTokens} by ~${overage} tokens`);
151844
- const maxDepth = getMaxCompressionDepth(db, sessionId);
151845
- const scoredCompartments = compartments.map((compartment, index) => {
151846
- const tokenEstimate = estimateTokens(`<compartment start="${compartment.startMessage}" end="${compartment.endMessage}" title="${compartment.title}">
151847
- ${compartment.content}
151848
- </compartment>
151849
- `);
151850
- const averageDepth = getAverageCompressionDepth(db, sessionId, compartment.startMessage, compartment.endMessage);
151851
- const normalizedAge = compartments.length > 1 ? 1 - index / (compartments.length - 1) : 1;
151852
- const normalizedDepth = maxDepth > 0 ? 1 - averageDepth / maxDepth : 1;
151853
- const score = 0.7 * normalizedAge + 0.3 * normalizedDepth;
151854
- return {
151855
- compartment,
151856
- index,
151857
- tokenEstimate,
151858
- averageDepth,
151859
- score
151860
- };
151861
- });
151862
- const sortedByScore = [...scoredCompartments].sort((left, right) => right.score - left.score || left.index - right.index);
151863
- const targetSelectionTokens = overage * 2;
151864
- let selectedCandidateTokens = 0;
151865
- const selectedCandidates = [];
151866
- for (const compartment of sortedByScore) {
151867
- if (selectedCandidateTokens >= targetSelectionTokens)
151868
- break;
151869
- selectedCandidates.push(compartment);
151870
- selectedCandidateTokens += compartment.tokenEstimate;
151871
- }
151872
- if (selectedCandidates.length < 2) {
151873
- sessionLog(sessionId, "compressor: not enough compartments to compress, skipping");
151874
- return false;
151875
- }
151876
- const selectedStartIndex = Math.min(...selectedCandidates.map((compartment) => compartment.index));
151877
- const selectedEndIndex = Math.max(...selectedCandidates.map((compartment) => compartment.index));
151878
- let selectedScoredCompartments = scoredCompartments.slice(selectedStartIndex, selectedEndIndex + 1);
151879
- const expandedTokens = selectedScoredCompartments.reduce((t, c) => t + c.tokenEstimate, 0);
151880
- if (expandedTokens > selectedCandidateTokens * 3) {
151881
- const sortedByIndex = [...selectedCandidates].sort((a, b) => a.index - b.index);
151882
- let runEnd = sortedByIndex[0].index;
151883
- for (let i = 1;i < sortedByIndex.length; i++) {
151884
- if (sortedByIndex[i].index === runEnd + 1) {
151885
- runEnd = sortedByIndex[i].index;
151886
- } else {
151887
- break;
151888
- }
151889
- }
151890
- selectedScoredCompartments = scoredCompartments.slice(sortedByIndex[0].index, runEnd + 1);
151891
- sessionLog(sessionId, `compressor: contiguous expansion was ${expandedTokens} tokens (>${selectedCandidateTokens * 3}), fell back to contiguous run of ${selectedScoredCompartments.length}`);
151892
- }
151893
- if (selectedScoredCompartments.length < 2) {
151894
- sessionLog(sessionId, "compressor: not enough adjacent compartments to compress, skipping");
151895
- return false;
151896
- }
151897
- const selectedCompartments = selectedScoredCompartments.map((scoredCompartment) => scoredCompartment.compartment);
151898
- const selectedTokens = selectedScoredCompartments.reduce((total, scoredCompartment) => total + scoredCompartment.tokenEstimate, 0);
151899
- const targetTokens = Math.floor(selectedTokens / 2);
151900
- const minAverageDepth = Math.min(...selectedScoredCompartments.map((compartment) => compartment.averageDepth));
151901
- const maxAverageDepth = Math.max(...selectedScoredCompartments.map((compartment) => compartment.averageDepth));
151902
- const minScore = Math.min(...selectedScoredCompartments.map((compartment) => compartment.score));
151903
- const maxScore = Math.max(...selectedScoredCompartments.map((compartment) => compartment.score));
151904
- sessionLog(sessionId, `compressor: scored ${compartments.length} compartments, selected ${selectedCompartments.length} (avg_depth range: ${minAverageDepth.toFixed(1)}-${maxAverageDepth.toFixed(1)}, score range: ${minScore.toFixed(1)}-${maxScore.toFixed(1)})`);
151905
- const overallAverageDepth = selectedScoredCompartments.reduce((sum, c) => sum + c.averageDepth, 0) / selectedScoredCompartments.length;
151906
- const depthTier = overallAverageDepth < 2 ? "preserve U: lines" : overallAverageDepth < 3 ? "condense U: lines" : "fold U: into prose";
151907
- sessionLog(sessionId, `compressor: selected contiguous range ${selectedCompartments[0].startMessage}-${selectedCompartments[selectedCompartments.length - 1].endMessage} (~${selectedTokens} tokens), target ~${targetTokens} tokens, avg_depth=${overallAverageDepth.toFixed(1)} (${depthTier})`);
151908
- try {
151909
- const compressed = await runCompressorPass({
151910
- ...deps,
151911
- compartments: selectedCompartments,
151912
- currentTokens: selectedTokens,
151913
- targetTokens,
151914
- averageDepth: overallAverageDepth
151915
- });
151916
- if (!compressed) {
151917
- sessionLog(sessionId, "compressor: compression pass failed, keeping existing compartments");
151918
- return false;
151919
- }
151920
- const leadingCompartments = compartments.slice(0, selectedStartIndex);
151921
- const trailingCompartments = compartments.slice(selectedEndIndex + 1);
151922
- const allCompartments = [
151923
- ...leadingCompartments.map((c, i) => ({
151924
- sequence: i,
151925
- startMessage: c.startMessage,
151926
- endMessage: c.endMessage,
151927
- startMessageId: c.startMessageId,
151928
- endMessageId: c.endMessageId,
151929
- title: c.title,
151930
- content: c.content
151931
- })),
151932
- ...compressed.map((c, i) => ({
151933
- sequence: leadingCompartments.length + i,
151934
- startMessage: c.startMessage,
151935
- endMessage: c.endMessage,
151936
- startMessageId: c.startMessageId,
151937
- endMessageId: c.endMessageId,
151938
- title: c.title,
151939
- content: c.content
151940
- })),
151941
- ...trailingCompartments.map((c, i) => ({
151942
- sequence: leadingCompartments.length + compressed.length + i,
151943
- startMessage: c.startMessage,
151944
- endMessage: c.endMessage,
151945
- startMessageId: c.startMessageId,
151946
- endMessageId: c.endMessageId,
151947
- title: c.title,
151948
- content: c.content
151949
- }))
151950
- ];
151951
- const originalStart = selectedCompartments[0].startMessage;
151952
- const originalEnd = selectedCompartments[selectedCompartments.length - 1].endMessage;
151953
- const compressedStart = compressed[0].startMessage;
151954
- const compressedEnd = compressed[compressed.length - 1].endMessage;
151955
- if (compressedStart !== originalStart || compressedEnd !== originalEnd) {
151956
- sessionLog(sessionId, `compressor: compressed range ${compressedStart}-${compressedEnd} doesn't match original ${originalStart}-${originalEnd}, aborting`);
151957
- return false;
151958
- }
151959
- for (let i = 1;i < compressed.length; i++) {
151960
- const prev = compressed[i - 1];
151961
- const curr = compressed[i];
151962
- if (curr.startMessage <= prev.endMessage) {
151963
- sessionLog(sessionId, `compressor: overlap at compartment ${i}: prev ends ${prev.endMessage}, curr starts ${curr.startMessage}, aborting`);
151964
- return false;
151965
- }
151966
- if (curr.startMessage > prev.endMessage + 1) {
151967
- sessionLog(sessionId, `compressor: gap at compartment ${i}: prev ends ${prev.endMessage}, curr starts ${curr.startMessage}, aborting`);
151968
- return false;
151969
- }
151970
- }
151971
- replaceAllCompartmentState(db, sessionId, allCompartments, facts.map((f) => ({ category: f.category, content: f.content })));
151972
- incrementCompressionDepth(db, sessionId, originalStart, originalEnd);
151973
- sessionLog(sessionId, `compressor: replaced ${selectedCompartments.length} compartments with ${compressed.length} compressed compartments`);
151974
- sessionLog(sessionId, `compressor: incremented compression depth for messages ${originalStart}-${originalEnd}`);
151975
- return true;
151976
- } catch (error48) {
151977
- sessionLog(sessionId, "compressor: unexpected error:", getErrorMessage(error48));
151978
- return false;
151979
- }
151980
- }
151981
- async function runCompressorPass(args) {
151982
- const {
151983
- client,
151984
- sessionId,
151985
- directory,
151986
- compartments,
151987
- currentTokens,
151988
- targetTokens,
151989
- averageDepth,
151990
- historianTimeoutMs
151991
- } = args;
151992
- const prompt = buildCompressorPrompt(compartments, currentTokens, targetTokens, averageDepth);
151993
- let agentSessionId = null;
151994
- try {
151995
- const createResponse = await client.session.create({
151996
- body: {
151997
- parentID: sessionId,
151998
- title: "magic-context-compressor"
151999
- },
152000
- query: { directory }
152001
- });
152002
- const createdSession = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
152003
- agentSessionId = typeof createdSession?.id === "string" ? createdSession.id : null;
152004
- if (!agentSessionId) {
152005
- sessionLog(sessionId, "compressor: could not create child session");
152006
- return null;
152007
- }
152008
- await promptSyncWithModelSuggestionRetry(client, {
152009
- path: { id: agentSessionId },
152010
- query: { directory },
152011
- body: {
152012
- agent: HISTORIAN_AGENT2,
152013
- parts: [{ type: "text", text: prompt }]
152014
- }
152015
- }, { timeoutMs: historianTimeoutMs ?? DEFAULT_HISTORIAN_TIMEOUT_MS });
152016
- const messagesResponse = await client.session.messages({
152017
- path: { id: agentSessionId },
152018
- query: { directory }
152019
- });
152020
- const messages = normalizeSDKResponse(messagesResponse, [], {
152021
- preferResponseOnMissingData: true
152022
- });
152023
- const result = extractLatestAssistantText(messages);
152024
- if (!result) {
152025
- sessionLog(sessionId, "compressor: historian returned no output");
152026
- return null;
152027
- }
152028
- const parsed = parseCompartmentOutput(result);
152029
- if (parsed.compartments.length === 0) {
152030
- sessionLog(sessionId, "compressor: historian returned no compartments");
152031
- return null;
152032
- }
152033
- const messageIdMap = new Map;
152034
- for (const c of compartments) {
152035
- messageIdMap.set(c.startMessage, c.startMessageId);
152036
- messageIdMap.set(c.endMessage, c.endMessageId);
152037
- }
152038
- const mapped = parsed.compartments.map((pc) => {
152039
- const startId = messageIdMap.get(pc.startMessage) ?? "";
152040
- const endId = messageIdMap.get(pc.endMessage) ?? "";
152041
- if (!startId || !endId) {
152042
- sessionLog(sessionId, `compressor: messageId miss for ordinals ${pc.startMessage}\u2192${pc.endMessage} (startId=${startId || "MISSING"}, endId=${endId || "MISSING"})`);
152043
- }
152044
- return {
152045
- startMessage: pc.startMessage,
152046
- endMessage: pc.endMessage,
152047
- startMessageId: startId,
152048
- endMessageId: endId,
152049
- title: pc.title,
152050
- content: pc.content
152051
- };
152052
- });
152053
- const hasEmptyIds = mapped.some((c) => !c.startMessageId || !c.endMessageId);
152054
- if (hasEmptyIds) {
152055
- sessionLog(sessionId, "compressor: rejecting \u2014 one or more compartments have empty messageIds");
152056
- return null;
152057
- }
152058
- return mapped;
152059
- } catch (error48) {
152060
- sessionLog(sessionId, "compressor: historian call failed:", getErrorMessage(error48));
152061
- return null;
152062
- } finally {
152063
- if (agentSessionId) {
152064
- await client.session.delete({ path: { id: agentSessionId }, query: { directory } }).catch((e) => {
152065
- sessionLog(sessionId, "compressor: session cleanup failed:", getErrorMessage(e));
152066
- });
152067
- }
152068
- }
152069
- }
152070
- var HISTORIAN_AGENT2 = "historian";
152071
- var init_compartment_runner_compressor = __esm(() => {
152072
- init_magic_context();
152073
- init_storage();
152074
- init_shared();
152075
- init_assistant_message_extractor();
152076
- init_logger();
152077
- init_compartment_parser();
152078
- init_compartment_prompt();
152079
- init_read_session_formatting();
152080
- });
152081
-
152082
- // src/hooks/magic-context/compartment-runner-drop-queue.ts
152083
- function queueDropsForCompartmentalizedMessages(db, sessionId, upToMessageIndex) {
152084
- const tags = getTagsBySession(db, sessionId);
152085
- const rawTagKeys = new Set(getRawSessionTagKeysThrough(sessionId, upToMessageIndex));
152086
- let dropsQueued = 0;
152087
- for (const tag of tags) {
152088
- if (tag.status !== "active")
152089
- continue;
152090
- if (rawTagKeys.has(tag.messageId)) {
152091
- queuePendingOp(db, sessionId, tag.tagNumber, "drop");
152092
- dropsQueued += 1;
152093
- }
152094
- }
152095
- sessionLog(sessionId, `compartment agent: queued ${dropsQueued} drops for messages 0-${upToMessageIndex}`);
152096
- }
152097
- var init_compartment_runner_drop_queue = __esm(() => {
152098
- init_storage_ops();
152099
- init_storage_tags();
152100
- init_logger();
152101
- init_read_session_chunk();
152102
- });
152103
-
152104
151498
  // src/hooks/magic-context/compartment-runner-mapping.ts
152105
151499
  function mapParsedCompartmentsToChunk(compartments, chunk, sequenceOffset) {
152106
151500
  const mapped = [];
@@ -152130,15 +151524,19 @@ var init_compartment_runner_mapping = __esm(() => {
152130
151524
  });
152131
151525
 
152132
151526
  // src/hooks/magic-context/compartment-runner-validation.ts
152133
- function healCompartmentGaps(compartments) {
152134
- const MAX_HEALABLE_GAP = 15;
151527
+ function healCompartmentGaps(compartments, toolOnlyRanges = []) {
151528
+ const SAFETY_HEAL_GAP = 15;
152135
151529
  for (let i = 1;i < compartments.length; i++) {
152136
151530
  const prev = compartments[i - 1];
152137
151531
  const curr = compartments[i];
152138
- const expectedStart = prev.endMessage + 1;
152139
- const gapSize = curr.startMessage - expectedStart;
152140
- if (gapSize > 0 && gapSize <= MAX_HEALABLE_GAP) {
152141
- prev.endMessage = curr.startMessage - 1;
151532
+ const gapStart = prev.endMessage + 1;
151533
+ const gapEnd = curr.startMessage - 1;
151534
+ const gapSize = gapEnd - gapStart + 1;
151535
+ if (gapSize <= 0)
151536
+ continue;
151537
+ const fullyInsideToolOnly = toolOnlyRanges.some((range) => range.start <= gapStart && range.end >= gapEnd);
151538
+ if (fullyInsideToolOnly || gapSize <= SAFETY_HEAL_GAP) {
151539
+ prev.endMessage = gapEnd;
152142
151540
  }
152143
151541
  }
152144
151542
  }
@@ -152150,7 +151548,7 @@ function validateHistorianOutput(text, _sessionId, chunk, _priorCompartments, se
152150
151548
  error: "Historian returned no usable compartments."
152151
151549
  };
152152
151550
  }
152153
- healCompartmentGaps(parsed.compartments);
151551
+ healCompartmentGaps(parsed.compartments, chunk.toolOnlyRanges);
152154
151552
  const mapped = mapParsedCompartmentsToChunk(parsed.compartments, chunk, sequenceOffset);
152155
151553
  if (!mapped.ok) {
152156
151554
  return {
@@ -152269,7 +151667,7 @@ var init_compartment_runner_validation = __esm(() => {
152269
151667
  // src/hooks/magic-context/compartment-runner-historian.ts
152270
151668
  import { mkdirSync as mkdirSync3, unlinkSync, writeFileSync } from "fs";
152271
151669
  import { tmpdir as tmpdir2 } from "os";
152272
- import { join as join13 } from "path";
151670
+ import { join as join12 } from "path";
152273
151671
  async function runValidatedHistorianPass(args) {
152274
151672
  const firstRun = await runHistorianPrompt({
152275
151673
  ...args,
@@ -152285,8 +151683,14 @@ async function runValidatedHistorianPass(args) {
152285
151683
  }
152286
151684
  const firstValidation = validateHistorianOutput(firstRun.result, args.parentSessionId, args.chunk, args.priorCompartments, args.sequenceOffset);
152287
151685
  if (firstValidation.ok) {
151686
+ const finalResult = args.twoPass ? await runEditorPassOrFallback({
151687
+ ...args,
151688
+ draftXml: firstRun.result,
151689
+ draftValidation: firstValidation,
151690
+ draftDumpPath: firstRun.dumpPath
151691
+ }) : firstValidation;
152288
151692
  cleanupHistorianDump(args.parentSessionId, firstRun.dumpPath);
152289
- return firstValidation;
151693
+ return finalResult;
152290
151694
  }
152291
151695
  await args.callbacks?.onRepairRetry?.(firstValidation.error ?? "invalid compartment output");
152292
151696
  const repairPrompt = buildHistorianRepairPrompt(args.prompt, firstRun.result, firstValidation.error ?? "invalid compartment output");
@@ -152305,8 +151709,14 @@ async function runValidatedHistorianPass(args) {
152305
151709
  }
152306
151710
  const repairValidation = validateHistorianOutput(repairRun.result, args.parentSessionId, args.chunk, args.priorCompartments, args.sequenceOffset);
152307
151711
  if (repairValidation.ok) {
151712
+ const finalResult = args.twoPass ? await runEditorPassOrFallback({
151713
+ ...args,
151714
+ draftXml: repairRun.result,
151715
+ draftValidation: repairValidation,
151716
+ draftDumpPath: repairRun.dumpPath
151717
+ }) : repairValidation;
152308
151718
  cleanupHistorianDump(args.parentSessionId, repairRun.dumpPath);
152309
- return repairValidation;
151719
+ return finalResult;
152310
151720
  }
152311
151721
  return runFallbackHistorianPass({
152312
151722
  ...args,
@@ -152315,6 +151725,32 @@ async function runValidatedHistorianPass(args) {
152315
151725
  dumpPaths: [firstRun.dumpPath, repairRun.dumpPath]
152316
151726
  });
152317
151727
  }
151728
+ async function runEditorPassOrFallback(args) {
151729
+ sessionLog(args.parentSessionId, "historian two-pass: running editor on draft");
151730
+ const editorRun = await runHistorianPrompt({
151731
+ client: args.client,
151732
+ parentSessionId: args.parentSessionId,
151733
+ sessionDirectory: args.sessionDirectory,
151734
+ prompt: buildHistorianEditorPrompt(args.draftXml),
151735
+ timeoutMs: args.timeoutMs,
151736
+ dumpLabel: `${args.dumpLabelBase}-editor`,
151737
+ agentId: HISTORIAN_EDITOR_AGENT
151738
+ });
151739
+ if (!editorRun.ok || !editorRun.result) {
151740
+ sessionLog(args.parentSessionId, "historian two-pass: editor call failed", {
151741
+ error: editorRun.error
151742
+ });
151743
+ return args.draftValidation;
151744
+ }
151745
+ const editorValidation = validateHistorianOutput(editorRun.result, args.parentSessionId, args.chunk, args.priorCompartments, args.sequenceOffset);
151746
+ if (!editorValidation.ok) {
151747
+ sessionLog(args.parentSessionId, "historian two-pass: editor validation failed, falling back to draft", { error: editorValidation.error });
151748
+ return args.draftValidation;
151749
+ }
151750
+ cleanupHistorianDump(args.parentSessionId, editorRun.dumpPath);
151751
+ sessionLog(args.parentSessionId, "historian two-pass: editor accepted");
151752
+ return editorValidation;
151753
+ }
152318
151754
  async function runHistorianPrompt(args) {
152319
151755
  const {
152320
151756
  client,
@@ -152323,11 +151759,12 @@ async function runHistorianPrompt(args) {
152323
151759
  prompt,
152324
151760
  timeoutMs,
152325
151761
  dumpLabel,
152326
- modelOverride
151762
+ modelOverride,
151763
+ agentId = HISTORIAN_AGENT
152327
151764
  } = args;
152328
151765
  let agentSessionId = null;
152329
151766
  try {
152330
- sessionLog(parentSessionId, `historian: creating child session (model=${modelOverride ? `${modelOverride.providerID}/${modelOverride.modelID}` : `agent:${HISTORIAN_AGENT}`})`);
151767
+ sessionLog(parentSessionId, `historian: creating child session (agent=${agentId}, model=${modelOverride ? `${modelOverride.providerID}/${modelOverride.modelID}` : `agent:${agentId}`})`);
152331
151768
  const createResponse = await client.session.create({
152332
151769
  body: {
152333
151770
  parentID: parentSessionId,
@@ -152346,7 +151783,7 @@ async function runHistorianPrompt(args) {
152346
151783
  path: { id: agentSessionId },
152347
151784
  query: { directory: sessionDirectory },
152348
151785
  body: {
152349
- agent: HISTORIAN_AGENT,
151786
+ agent: agentId,
152350
151787
  ...modelOverride ? { model: modelOverride } : {},
152351
151788
  parts: [{ type: "text", text: prompt }]
152352
151789
  }
@@ -152454,8 +151891,8 @@ function isTransientHistorianPromptError(message) {
152454
151891
  ].some((token) => normalized.includes(token));
152455
151892
  }
152456
151893
  function sleep(ms) {
152457
- return new Promise((resolve2) => {
152458
- setTimeout(resolve2, ms);
151894
+ return new Promise((resolve3) => {
151895
+ setTimeout(resolve3, ms);
152459
151896
  });
152460
151897
  }
152461
151898
  function cleanupHistorianDump(sessionId, dumpPath) {
@@ -152475,7 +151912,7 @@ function dumpHistorianResponse(sessionId, label, text) {
152475
151912
  mkdirSync3(HISTORIAN_RESPONSE_DUMP_DIR, { recursive: true });
152476
151913
  const safeSessionId = sanitizeDumpName(sessionId);
152477
151914
  const safeLabel = sanitizeDumpName(label);
152478
- const dumpPath = join13(HISTORIAN_RESPONSE_DUMP_DIR, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
151915
+ const dumpPath = join12(HISTORIAN_RESPONSE_DUMP_DIR, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
152479
151916
  writeFileSync(dumpPath, text, "utf8");
152480
151917
  sessionLog(sessionId, "compartment agent: historian response dumped", {
152481
151918
  label,
@@ -152498,8 +151935,9 @@ var init_compartment_runner_historian = __esm(() => {
152498
151935
  init_magic_context();
152499
151936
  init_shared();
152500
151937
  init_assistant_message_extractor();
151938
+ init_compartment_prompt();
152501
151939
  init_compartment_runner_validation();
152502
- HISTORIAN_RESPONSE_DUMP_DIR = join13(tmpdir2(), "magic-context-historian");
151940
+ HISTORIAN_RESPONSE_DUMP_DIR = join12(tmpdir2(), "magic-context-historian");
152503
151941
  });
152504
151942
 
152505
151943
  // src/hooks/magic-context/compartment-runner-state-xml.ts
@@ -152540,6 +151978,131 @@ var init_compartment_runner_state_xml = __esm(() => {
152540
151978
  init_compartment_storage();
152541
151979
  });
152542
151980
 
151981
+ // src/features/magic-context/memory/constants.ts
151982
+ var PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, CATEGORY_DEFAULT_TTL;
151983
+ var init_constants = __esm(() => {
151984
+ PROMOTABLE_CATEGORIES = [
151985
+ "ARCHITECTURE_DECISIONS",
151986
+ "CONSTRAINTS",
151987
+ "CONFIG_DEFAULTS",
151988
+ "NAMING",
151989
+ "USER_PREFERENCES",
151990
+ "USER_DIRECTIVES",
151991
+ "ENVIRONMENT",
151992
+ "WORKFLOW_RULES",
151993
+ "KNOWN_ISSUES"
151994
+ ];
151995
+ CATEGORY_PRIORITY = [
151996
+ "USER_DIRECTIVES",
151997
+ "USER_PREFERENCES",
151998
+ "NAMING",
151999
+ "CONFIG_DEFAULTS",
152000
+ "CONSTRAINTS",
152001
+ "ARCHITECTURE_DECISIONS",
152002
+ "ENVIRONMENT",
152003
+ "WORKFLOW_RULES",
152004
+ "KNOWN_ISSUES"
152005
+ ];
152006
+ CATEGORY_DEFAULT_TTL = {
152007
+ WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
152008
+ KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
152009
+ };
152010
+ });
152011
+
152012
+ // src/hooks/magic-context/temporal-awareness.ts
152013
+ function formatGap(seconds) {
152014
+ if (!Number.isFinite(seconds) || seconds < TEMPORAL_AWARENESS_THRESHOLD_SECONDS) {
152015
+ return null;
152016
+ }
152017
+ if (seconds < SECONDS_PER_HOUR) {
152018
+ const minutes = Math.floor(seconds / SECONDS_PER_MINUTE);
152019
+ return `+${minutes}m`;
152020
+ }
152021
+ if (seconds < SECONDS_PER_DAY) {
152022
+ const hours = Math.floor(seconds / SECONDS_PER_HOUR);
152023
+ const minutes = Math.floor((seconds - hours * SECONDS_PER_HOUR) / SECONDS_PER_MINUTE);
152024
+ return minutes === 0 ? `+${hours}h` : `+${hours}h ${minutes}m`;
152025
+ }
152026
+ if (seconds < SECONDS_PER_WEEK) {
152027
+ const days2 = Math.floor(seconds / SECONDS_PER_DAY);
152028
+ const hours = Math.floor((seconds - days2 * SECONDS_PER_DAY) / SECONDS_PER_HOUR);
152029
+ return hours === 0 ? `+${days2}d` : `+${days2}d ${hours}h`;
152030
+ }
152031
+ const weeks = Math.floor(seconds / SECONDS_PER_WEEK);
152032
+ const days = Math.floor((seconds - weeks * SECONDS_PER_WEEK) / SECONDS_PER_DAY);
152033
+ return days === 0 ? `+${weeks}w` : `+${weeks}w ${days}d`;
152034
+ }
152035
+ function formatDate(ms) {
152036
+ const d = new Date(ms);
152037
+ const yyyy = d.getFullYear().toString().padStart(4, "0");
152038
+ const mm = (d.getMonth() + 1).toString().padStart(2, "0");
152039
+ const dd = d.getDate().toString().padStart(2, "0");
152040
+ return `${yyyy}-${mm}-${dd}`;
152041
+ }
152042
+ function temporalMarkerPrefix(seconds) {
152043
+ const marker = formatGap(seconds);
152044
+ if (!marker)
152045
+ return null;
152046
+ return `<!-- ${marker} -->
152047
+ `;
152048
+ }
152049
+ function isMutableTextPart(part) {
152050
+ if (part === null || typeof part !== "object")
152051
+ return false;
152052
+ const p = part;
152053
+ return p.type === "text" && typeof p.text === "string";
152054
+ }
152055
+ function findFirstVisibleTextPart(parts) {
152056
+ for (const p of parts) {
152057
+ if (!isMutableTextPart(p))
152058
+ continue;
152059
+ if (p.ignored === true)
152060
+ continue;
152061
+ return p;
152062
+ }
152063
+ return null;
152064
+ }
152065
+ function injectTemporalMarkers(messages) {
152066
+ let injected = 0;
152067
+ let prev = null;
152068
+ for (const raw of messages) {
152069
+ if (!raw || typeof raw !== "object")
152070
+ continue;
152071
+ const msg = raw;
152072
+ const role = msg.info?.role;
152073
+ if (prev !== null && role === "user") {
152074
+ const prevTime = prev.info?.time;
152075
+ const currTime = msg.info?.time;
152076
+ if (prevTime?.created !== undefined && currTime?.created !== undefined) {
152077
+ const prevEnd = prevTime.completed ?? prevTime.created;
152078
+ const gapSec = (currTime.created - prevEnd) / 1000;
152079
+ const prefix = temporalMarkerPrefix(gapSec);
152080
+ if (prefix && Array.isArray(msg.parts)) {
152081
+ const target = findFirstVisibleTextPart(msg.parts);
152082
+ if (target && typeof target.text === "string") {
152083
+ const tagMatch = target.text.match(/^(?:\u00A7\d+\u00A7\s*)+/);
152084
+ const tagPrefix = tagMatch ? tagMatch[0] : "";
152085
+ const body = target.text.slice(tagPrefix.length);
152086
+ if (!TEMPORAL_MARKER_PATTERN.test(body)) {
152087
+ target.text = tagPrefix + prefix + body;
152088
+ injected++;
152089
+ }
152090
+ }
152091
+ }
152092
+ }
152093
+ }
152094
+ prev = msg;
152095
+ }
152096
+ return injected;
152097
+ }
152098
+ var TEMPORAL_AWARENESS_THRESHOLD_SECONDS = 300, SECONDS_PER_MINUTE = 60, SECONDS_PER_HOUR, SECONDS_PER_DAY, SECONDS_PER_WEEK, TEMPORAL_MARKER_PATTERN;
152099
+ var init_temporal_awareness = __esm(() => {
152100
+ SECONDS_PER_HOUR = 60 * 60;
152101
+ SECONDS_PER_DAY = 24 * 60 * 60;
152102
+ SECONDS_PER_WEEK = 7 * 24 * 60 * 60;
152103
+ TEMPORAL_MARKER_PATTERN = /^<!-- \+[\d]+[mhdw](?: [\d]+[mhdw])? -->\n/;
152104
+ });
152105
+
152543
152106
  // src/hooks/magic-context/inject-compartments.ts
152544
152107
  function clearInjectionCache(sessionId) {
152545
152108
  injectionCache.delete(sessionId);
@@ -152609,7 +152172,7 @@ function trimMemoriesToBudget(sessionId, memories, budgetTokens) {
152609
152172
  }
152610
152173
  return result;
152611
152174
  }
152612
- function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, projectPath, injectionBudgetTokens) {
152175
+ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, projectPath, injectionBudgetTokens, temporalAwareness) {
152613
152176
  const cached2 = injectionCache.get(sessionId);
152614
152177
  if (!isCacheBusting && cached2) {
152615
152178
  if (cached2.compartmentEndMessageId.length > 0) {
@@ -152655,7 +152218,28 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
152655
152218
  injectionCache.delete(sessionId);
152656
152219
  return null;
152657
152220
  }
152658
- const block = buildCompartmentBlock(compartments, facts, memoryBlock);
152221
+ let dateRanges;
152222
+ if (temporalAwareness && compartments.length > 0) {
152223
+ const ids = new Set;
152224
+ for (const c of compartments) {
152225
+ if (c.startMessageId)
152226
+ ids.add(c.startMessageId);
152227
+ if (c.endMessageId)
152228
+ ids.add(c.endMessageId);
152229
+ }
152230
+ const times = getMessageTimesFromOpenCodeDb(sessionId, Array.from(ids));
152231
+ const byId = new Map;
152232
+ for (const c of compartments) {
152233
+ const startMs = times.get(c.startMessageId);
152234
+ const endMs = times.get(c.endMessageId);
152235
+ if (startMs !== undefined && endMs !== undefined) {
152236
+ byId.set(c.id, { start: formatDate(startMs), end: formatDate(endMs) });
152237
+ }
152238
+ }
152239
+ if (byId.size > 0)
152240
+ dateRanges = { byId };
152241
+ }
152242
+ const block = buildCompartmentBlock(compartments, facts, memoryBlock, dateRanges);
152659
152243
  if (compartments.length === 0) {
152660
152244
  const result2 = {
152661
152245
  block,
@@ -152757,11 +152341,1523 @@ var init_inject_compartments = __esm(() => {
152757
152341
  init_constants();
152758
152342
  init_storage_memory();
152759
152343
  init_logger();
152344
+ init_read_session_db();
152760
152345
  init_read_session_formatting();
152346
+ init_temporal_awareness();
152761
152347
  injectionCache = new Map;
152762
152348
  CONSTRAINT_KEYWORDS = /\b(must|never|always|cannot|should not|must not)\b/i;
152763
152349
  });
152764
152350
 
152351
+ // src/hooks/magic-context/send-session-notification.ts
152352
+ var exports_send_session_notification = {};
152353
+ __export(exports_send_session_notification, {
152354
+ sendUserPrompt: () => sendUserPrompt,
152355
+ sendIgnoredMessage: () => sendIgnoredMessage
152356
+ });
152357
+ function hasNotificationSessionClient(client) {
152358
+ if (client === null || typeof client !== "object")
152359
+ return false;
152360
+ const candidate = client;
152361
+ if (candidate.session === undefined)
152362
+ return true;
152363
+ if (candidate.session === null || typeof candidate.session !== "object")
152364
+ return false;
152365
+ const session = candidate.session;
152366
+ return (session.prompt === undefined || typeof session.prompt === "function") && (session.promptAsync === undefined || typeof session.promptAsync === "function");
152367
+ }
152368
+ function inferToastVariant(text) {
152369
+ const lower = text.toLowerCase();
152370
+ if (lower.includes("error") || lower.includes("failed") || lower.includes("alert"))
152371
+ return "error";
152372
+ if (lower.includes("warning") || lower.includes("\u26A0"))
152373
+ return "warning";
152374
+ if (lower.includes("complete") || lower.includes("success") || lower.includes("\u2713") || lower.includes("finished"))
152375
+ return "success";
152376
+ return "info";
152377
+ }
152378
+ function extractToastTitle(text) {
152379
+ const headingMatch = text.match(/^#+\s+(.+)/m);
152380
+ if (headingMatch)
152381
+ return headingMatch[1].trim();
152382
+ const firstLine = text.split(`
152383
+ `)[0].trim();
152384
+ if (firstLine.length <= 80)
152385
+ return firstLine;
152386
+ return "Magic Context";
152387
+ }
152388
+ async function sendIgnoredMessage(client, sessionId, text, params) {
152389
+ const { isTuiConnected: checkTui } = await Promise.resolve().then(() => (init_rpc_notifications(), exports_rpc_notifications));
152390
+ if (checkTui()) {
152391
+ try {
152392
+ const c2 = client;
152393
+ const tui = c2?.tui;
152394
+ if (typeof tui?.showToast === "function") {
152395
+ const tuiClient = tui;
152396
+ await tuiClient.showToast({
152397
+ body: {
152398
+ title: extractToastTitle(text),
152399
+ message: text.length > 200 ? `${text.slice(0, 200)}\u2026` : text,
152400
+ variant: inferToastVariant(text),
152401
+ duration: 5000
152402
+ }
152403
+ }).catch(() => {});
152404
+ return;
152405
+ }
152406
+ } catch {}
152407
+ }
152408
+ const agent = params.agent || undefined;
152409
+ const variant = params.variant || undefined;
152410
+ const model = params.providerId && params.modelId ? {
152411
+ providerID: params.providerId,
152412
+ modelID: params.modelId
152413
+ } : undefined;
152414
+ if (!hasNotificationSessionClient(client)) {
152415
+ sessionLog(sessionId, "session prompt API unavailable for notification");
152416
+ return;
152417
+ }
152418
+ const c = client;
152419
+ const input = {
152420
+ path: { id: sessionId },
152421
+ body: {
152422
+ noReply: true,
152423
+ agent,
152424
+ model,
152425
+ variant,
152426
+ parts: [
152427
+ {
152428
+ type: "text",
152429
+ text,
152430
+ ignored: true
152431
+ }
152432
+ ]
152433
+ }
152434
+ };
152435
+ try {
152436
+ if (typeof c.session?.prompt === "function") {
152437
+ await Promise.resolve(c.session.prompt(input));
152438
+ } else if (typeof c.session?.promptAsync === "function") {
152439
+ await c.session.promptAsync(input);
152440
+ } else {
152441
+ sessionLog(sessionId, "session prompt API unavailable for notification");
152442
+ }
152443
+ } catch (error48) {
152444
+ const msg = getErrorMessage(error48);
152445
+ sessionLog(sessionId, "failed to send notification:", msg);
152446
+ }
152447
+ }
152448
+ async function sendUserPrompt(client, sessionId, text) {
152449
+ if (!hasNotificationSessionClient(client)) {
152450
+ sessionLog(sessionId, "session prompt API unavailable for user prompt");
152451
+ return;
152452
+ }
152453
+ const c = client;
152454
+ const input = {
152455
+ path: { id: sessionId },
152456
+ body: {
152457
+ parts: [{ type: "text", text }]
152458
+ }
152459
+ };
152460
+ try {
152461
+ if (typeof c.session?.promptAsync === "function") {
152462
+ await c.session.promptAsync(input);
152463
+ } else if (typeof c.session?.prompt === "function") {
152464
+ await Promise.resolve(c.session.prompt(input));
152465
+ } else {
152466
+ sessionLog(sessionId, "session prompt API unavailable for user prompt");
152467
+ }
152468
+ } catch (error48) {
152469
+ const msg = getErrorMessage(error48);
152470
+ sessionLog(sessionId, "failed to send user prompt:", msg);
152471
+ }
152472
+ }
152473
+ var init_send_session_notification = __esm(() => {
152474
+ init_logger();
152475
+ });
152476
+
152477
+ // src/hooks/magic-context/compartment-runner-partial-recomp.ts
152478
+ function snapRangeToCompartments(compartments, range) {
152479
+ if (compartments.length === 0) {
152480
+ return {
152481
+ error: "No compartments exist yet for this session. Run `/ctx-recomp` (full) first, then use partial recomp to refine specific ranges."
152482
+ };
152483
+ }
152484
+ const sorted = compartments.slice().sort((a, b) => a.sequence - b.sequence);
152485
+ const { start, end } = range;
152486
+ if (start < 1)
152487
+ return { error: `Start must be >= 1 (got ${start}).` };
152488
+ if (end < start)
152489
+ return { error: `End must be >= start (got ${start}-${end}).` };
152490
+ const firstEnclosingIdx = sorted.findIndex((c) => c.endMessage >= start);
152491
+ if (firstEnclosingIdx === -1) {
152492
+ const last = sorted[sorted.length - 1];
152493
+ return {
152494
+ error: `Range ${start}-${end} starts after the last compartment (which ends at message ${last.endMessage}). Nothing to rebuild.`
152495
+ };
152496
+ }
152497
+ let lastEnclosingIdx = -1;
152498
+ for (let i = sorted.length - 1;i >= 0; i--) {
152499
+ if (sorted[i].startMessage <= end) {
152500
+ lastEnclosingIdx = i;
152501
+ break;
152502
+ }
152503
+ }
152504
+ if (lastEnclosingIdx === -1 || lastEnclosingIdx < firstEnclosingIdx) {
152505
+ return {
152506
+ error: `Range ${start}-${end} does not overlap any compartment.`
152507
+ };
152508
+ }
152509
+ return {
152510
+ snapStart: sorted[firstEnclosingIdx].startMessage,
152511
+ snapEnd: sorted[lastEnclosingIdx].endMessage,
152512
+ priorCompartments: sorted.slice(0, firstEnclosingIdx),
152513
+ rangeCompartments: sorted.slice(firstEnclosingIdx, lastEnclosingIdx + 1),
152514
+ tailCompartments: sorted.slice(lastEnclosingIdx + 1)
152515
+ };
152516
+ }
152517
+ function compartmentToInput(c, newSequence) {
152518
+ return {
152519
+ sequence: newSequence,
152520
+ startMessage: c.startMessage,
152521
+ endMessage: c.endMessage,
152522
+ startMessageId: c.startMessageId,
152523
+ endMessageId: c.endMessageId,
152524
+ title: c.title,
152525
+ content: c.content
152526
+ };
152527
+ }
152528
+ async function executePartialRecompInternal(deps, range) {
152529
+ const {
152530
+ client,
152531
+ db,
152532
+ sessionId,
152533
+ historianChunkTokens,
152534
+ directory,
152535
+ historianTimeoutMs,
152536
+ getNotificationParams
152537
+ } = deps;
152538
+ const notifParams = () => getNotificationParams?.() ?? {};
152539
+ updateSessionMeta(db, sessionId, { compartmentInProgress: true });
152540
+ try {
152541
+ let promoteFinal = function() {
152542
+ const newBuilt = candidateCompartments.slice(priorCompartments.length);
152543
+ if (newBuilt.length === 0)
152544
+ return null;
152545
+ const newBuiltError = (() => {
152546
+ let expected = snapStart;
152547
+ for (const c of newBuilt) {
152548
+ if (c.startMessage !== expected) {
152549
+ return c.startMessage < expected ? `overlap in rebuilt range near ${expected}` : `gap in rebuilt range before ${c.startMessage} (expected ${expected})`;
152550
+ }
152551
+ if (c.endMessage < c.startMessage) {
152552
+ return `invalid range ${c.startMessage}-${c.endMessage}`;
152553
+ }
152554
+ expected = c.endMessage + 1;
152555
+ }
152556
+ if (expected - 1 !== snapEnd) {
152557
+ return `rebuilt range ends at ${expected - 1} but snapped end is ${snapEnd}`;
152558
+ }
152559
+ return null;
152560
+ })();
152561
+ if (newBuiltError) {
152562
+ log(`[magic-context] partial recomp validation failed: ${newBuiltError}`);
152563
+ return null;
152564
+ }
152565
+ const merged = [
152566
+ ...candidateCompartments,
152567
+ ...tailCompartments.map((c, idx) => compartmentToInput(c, candidateCompartments.length + idx))
152568
+ ];
152569
+ const mergedError = validateStoredCompartments(merged);
152570
+ if (mergedError) {
152571
+ log(`[magic-context] partial recomp merged validation failed: ${mergedError}`);
152572
+ return null;
152573
+ }
152574
+ saveRecompStagingPass(db, sessionId, passCount + 1, merged, currentFacts);
152575
+ const promoted = promoteRecompStaging(db, sessionId);
152576
+ if (!promoted) {
152577
+ log("[magic-context] partial recomp promote returned null");
152578
+ return null;
152579
+ }
152580
+ setRecompPartialRange(db, sessionId, null);
152581
+ clearCompressionDepthRange(db, sessionId, snapStart, snapEnd);
152582
+ clearInjectionCache(sessionId);
152583
+ const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
152584
+ return { compartmentCount: merged.length, lastEndMessage: lastEnd };
152585
+ };
152586
+ const protectedTailStart = getProtectedTailStartOrdinal(sessionId);
152587
+ const existingCompartments = getCompartments(db, sessionId);
152588
+ const snapResult = snapRangeToCompartments(existingCompartments, range);
152589
+ if ("error" in snapResult) {
152590
+ return `## Magic Recomp \u2014 Failed
152591
+
152592
+ ${snapResult.error}`;
152593
+ }
152594
+ const { snapStart, snapEnd, priorCompartments, tailCompartments } = snapResult;
152595
+ if (snapEnd >= protectedTailStart) {
152596
+ return `## Magic Recomp \u2014 Failed
152597
+
152598
+ Snapped range ${snapStart}-${snapEnd} would cross into the protected tail (starting at ${protectedTailStart}). Partial recomp cannot rebuild recent messages. Try an earlier range.`;
152599
+ }
152600
+ const storedRange = getRecompPartialRange(db, sessionId);
152601
+ const existingStaging = getRecompStaging(db, sessionId);
152602
+ if (existingStaging && storedRange && (storedRange.start !== snapStart || storedRange.end !== snapEnd)) {
152603
+ return [
152604
+ "## Magic Recomp \u2014 Failed",
152605
+ "",
152606
+ `An unfinished partial recomp is already staged for range ${storedRange.start}-${storedRange.end}, which does not match the requested range ${snapStart}-${snapEnd}.`,
152607
+ "",
152608
+ "Resume that range by running `/ctx-recomp` with the same original arguments,",
152609
+ "or cancel it by running `/ctx-flush` before starting a new partial recomp."
152610
+ ].join(`
152611
+ `);
152612
+ }
152613
+ if (existingStaging && !storedRange) {
152614
+ return [
152615
+ "## Magic Recomp \u2014 Failed",
152616
+ "",
152617
+ "An unfinished full recomp is already staged for this session.",
152618
+ "Resume it by running `/ctx-recomp` without arguments,",
152619
+ "or cancel it before starting a partial recomp."
152620
+ ].join(`
152621
+ `);
152622
+ }
152623
+ const currentFacts = getSessionFacts(db, sessionId).map((f) => ({
152624
+ category: f.category,
152625
+ content: f.content
152626
+ }));
152627
+ const parentSessionResponse = await client.session.get({ path: { id: sessionId } }).catch(() => null);
152628
+ const parentSession = normalizeSDKResponse(parentSessionResponse, null, { preferResponseOnMissingData: true });
152629
+ const sessionDirectory = parentSession?.directory ?? directory;
152630
+ const projectPath = directory ? resolveProjectIdentity(directory) : undefined;
152631
+ const memories = projectPath ? getMemoriesByProject(db, projectPath, ["active", "permanent"]) : [];
152632
+ const memoryBlockForExistingState = memories.length > 0 ? undefined : undefined;
152633
+ let candidateCompartments;
152634
+ let passCount;
152635
+ let offset;
152636
+ const resumed = existingStaging !== null && storedRange !== null;
152637
+ if (resumed && existingStaging) {
152638
+ candidateCompartments = existingStaging.compartments;
152639
+ passCount = existingStaging.passCount;
152640
+ const lastInStaging = existingStaging.lastEndMessage;
152641
+ offset = lastInStaging >= snapStart ? lastInStaging + 1 : snapStart;
152642
+ } else {
152643
+ candidateCompartments = priorCompartments.map((c, idx) => compartmentToInput(c, idx));
152644
+ passCount = 0;
152645
+ offset = snapStart;
152646
+ saveRecompStagingPass(db, sessionId, 0, candidateCompartments, currentFacts);
152647
+ setRecompPartialRange(db, sessionId, { start: snapStart, end: snapEnd });
152648
+ }
152649
+ let currentTokenBudget = historianChunkTokens;
152650
+ let passAttempt = 1;
152651
+ await sendIgnoredMessage(client, sessionId, resumed ? `## Magic Recomp \u2014 Resumed (Partial)
152652
+
152653
+ Found ${candidateCompartments.length - priorCompartments.length} newly built compartment(s) from ${passCount} previous pass(es), covering messages ${snapStart}-${offset - 1}. Resuming from message ${offset} toward ${snapEnd}.` : `## Magic Recomp \u2014 Partial
152654
+
152655
+ Snapped to compartment boundaries: rebuilding messages ${snapStart}-${snapEnd} (${tailCompartments.length} tail compartment(s) preserved).`, notifParams());
152656
+ while (offset <= snapEnd) {
152657
+ const chunk = readSessionChunk(sessionId, currentTokenBudget, offset, snapEnd + 1);
152658
+ if (!chunk.text || chunk.messageCount === 0 || chunk.endIndex < offset) {
152659
+ return `## Magic Recomp \u2014 Failed
152660
+
152661
+ Recomp stopped because raw history ${offset}-${snapEnd} could not be turned into a valid historian chunk. Partial recomp preserved original state (staging kept for retry).`;
152662
+ }
152663
+ const chunkCoverageError = validateChunkCoverage(chunk);
152664
+ if (chunkCoverageError) {
152665
+ return `## Magic Recomp \u2014 Failed
152666
+
152667
+ Partial recomp stopped because the raw chunk could not be represented safely: ${chunkCoverageError}
152668
+
152669
+ Original state preserved (staging kept for retry).`;
152670
+ }
152671
+ const existingState = buildExistingStateXml(candidateCompartments, currentFacts, undefined);
152672
+ const prompt = buildCompartmentAgentPrompt(existingState, `Messages ${chunk.startIndex}-${chunk.endIndex}:
152673
+
152674
+ ${chunk.text}`);
152675
+ await sendIgnoredMessage(client, sessionId, `## Magic Recomp \u2014 Partial
152676
+
152677
+ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${chunk.startIndex}-${chunk.endIndex}.`, notifParams());
152678
+ const validatedPass = await runValidatedHistorianPass({
152679
+ client,
152680
+ parentSessionId: sessionId,
152681
+ sessionDirectory,
152682
+ prompt,
152683
+ chunk,
152684
+ priorCompartments: candidateCompartments,
152685
+ sequenceOffset: candidateCompartments.length,
152686
+ dumpLabelBase: `partial-recomp-${sessionId}-${chunk.startIndex}-${chunk.endIndex}-pass-${passCount + 1}`,
152687
+ timeoutMs: historianTimeoutMs,
152688
+ fallbackModelId: deps.fallbackModelId,
152689
+ twoPass: deps.historianTwoPass,
152690
+ callbacks: {
152691
+ onRepairRetry: async (error48) => {
152692
+ await sendIgnoredMessage(client, sessionId, `## Magic Recomp \u2014 Partial
152693
+
152694
+ Historian pass ${passCount + 1}, attempt ${passAttempt} is continuing with a repair retry for messages ${chunk.startIndex}-${chunk.endIndex}.
152695
+
152696
+ The previous output did not validate: ${error48}`, notifParams());
152697
+ }
152698
+ }
152699
+ });
152700
+ if (!validatedPass.ok) {
152701
+ const reducedBudget = getReducedRecompTokenBudget(currentTokenBudget);
152702
+ if (reducedBudget !== null) {
152703
+ const smallerChunk = readSessionChunk(sessionId, reducedBudget, offset, snapEnd + 1);
152704
+ if (smallerChunk.messageCount > 0 && smallerChunk.endIndex < chunk.endIndex) {
152705
+ await sendIgnoredMessage(client, sessionId, `## Magic Recomp \u2014 Partial
152706
+
152707
+ Historian pass ${passCount + 1}, attempt ${passAttempt} is continuing with a smaller chunk ending at ${smallerChunk.endIndex} because messages ${chunk.startIndex}-${chunk.endIndex} could not be validated.
152708
+
152709
+ Validator result: ${validatedPass.error}`, notifParams());
152710
+ currentTokenBudget = reducedBudget;
152711
+ passAttempt += 1;
152712
+ continue;
152713
+ }
152714
+ }
152715
+ return `## Magic Recomp \u2014 Failed
152716
+
152717
+ Partial recomp failed while rebuilding messages ${chunk.startIndex}-${chunk.endIndex}: ${validatedPass.error}
152718
+
152719
+ Original state preserved (staging kept for retry).`;
152720
+ }
152721
+ candidateCompartments = [
152722
+ ...candidateCompartments,
152723
+ ...validatedPass.compartments ?? []
152724
+ ];
152725
+ passCount += 1;
152726
+ currentTokenBudget = historianChunkTokens;
152727
+ passAttempt = 1;
152728
+ saveRecompStagingPass(db, sessionId, passCount, candidateCompartments, currentFacts);
152729
+ const nextOffset = (validatedPass.compartments?.[validatedPass.compartments.length - 1]?.endMessage ?? chunk.endIndex) + 1;
152730
+ if (nextOffset <= offset) {
152731
+ return `## Magic Recomp \u2014 Failed
152732
+
152733
+ Partial recomp made no forward progress after messages ${chunk.startIndex}-${chunk.endIndex}. Staging kept for retry.`;
152734
+ }
152735
+ offset = nextOffset;
152736
+ }
152737
+ const finalResult = promoteFinal();
152738
+ if (!finalResult) {
152739
+ return `## Magic Recomp \u2014 Failed
152740
+
152741
+ Partial recomp completed historian passes but the final compartment set failed validation. Original state preserved (staging kept for inspection).`;
152742
+ }
152743
+ return [
152744
+ "## Magic Recomp \u2014 Partial Complete",
152745
+ "",
152746
+ ...resumed ? ["Resumed from previous interrupted partial run."] : [],
152747
+ `Rebuilt compartments covering messages ${snapStart}-${snapEnd} using ${passCount} historian pass${passCount === 1 ? "" : "es"}.`,
152748
+ `Preserved ${priorCompartments.length} prior compartment(s) and ${tailCompartments.length} tail compartment(s) unchanged.`,
152749
+ `Facts unchanged (${currentFacts.length} entr${currentFacts.length === 1 ? "y" : "ies"}).`,
152750
+ `Total compartments: ${finalResult.compartmentCount}.`
152751
+ ].join(`
152752
+ `);
152753
+ } catch (error48) {
152754
+ const message = getErrorMessage(error48);
152755
+ return `## Magic Recomp \u2014 Failed
152756
+
152757
+ Partial recomp failed unexpectedly: ${message}
152758
+
152759
+ Staging preserved for resume on next attempt.`;
152760
+ } finally {
152761
+ updateSessionMeta(db, sessionId, { compartmentInProgress: false });
152762
+ const leftoverStaging = getRecompStaging(db, sessionId);
152763
+ const leftoverRange = getRecompPartialRange(db, sessionId);
152764
+ if (leftoverStaging && leftoverRange) {} else if (leftoverStaging && !leftoverRange) {
152765
+ log(`[magic-context] partial recomp cleanup: clearing orphaned staging without range marker for session ${sessionId}`);
152766
+ clearRecompStaging(db, sessionId);
152767
+ }
152768
+ }
152769
+ }
152770
+ var init_compartment_runner_partial_recomp = __esm(() => {
152771
+ init_compartment_storage();
152772
+ init_compression_depth_storage();
152773
+ init_project_identity();
152774
+ init_storage_memory();
152775
+ init_storage_meta();
152776
+ init_shared();
152777
+ init_logger();
152778
+ init_compartment_prompt();
152779
+ init_compartment_runner_historian();
152780
+ init_compartment_runner_state_xml();
152781
+ init_compartment_runner_validation();
152782
+ init_inject_compartments();
152783
+ init_read_session_chunk();
152784
+ init_send_session_notification();
152785
+ });
152786
+
152787
+ // src/hooks/magic-context/derive-budgets.ts
152788
+ var exports_derive_budgets = {};
152789
+ __export(exports_derive_budgets, {
152790
+ resolveHistorianContextLimit: () => resolveHistorianContextLimit,
152791
+ deriveTriggerBudget: () => deriveTriggerBudget,
152792
+ deriveHistorianChunkTokens: () => deriveHistorianChunkTokens
152793
+ });
152794
+ function deriveTriggerBudget(mainContextLimit, executeThresholdPercentage) {
152795
+ if (!Number.isFinite(mainContextLimit) || mainContextLimit <= 0) {
152796
+ return TRIGGER_BUDGET_MIN;
152797
+ }
152798
+ const thresholdFraction = Math.max(0, executeThresholdPercentage) / 100;
152799
+ const usable = mainContextLimit * thresholdFraction;
152800
+ const derived = Math.round(usable * TRIGGER_BUDGET_PERCENTAGE);
152801
+ return Math.max(TRIGGER_BUDGET_MIN, Math.min(TRIGGER_BUDGET_MAX, derived));
152802
+ }
152803
+ function deriveHistorianChunkTokens(historianContextLimit) {
152804
+ if (!Number.isFinite(historianContextLimit) || historianContextLimit <= 0) {
152805
+ return HISTORIAN_CHUNK_MIN;
152806
+ }
152807
+ const derived = Math.round(historianContextLimit * HISTORIAN_CHUNK_PERCENTAGE);
152808
+ return Math.max(HISTORIAN_CHUNK_MIN, Math.min(HISTORIAN_CHUNK_MAX, derived));
152809
+ }
152810
+ function resolveHistorianContextLimit(historianModelOverride) {
152811
+ if (typeof historianModelOverride === "string" && historianModelOverride.includes("/")) {
152812
+ const [providerID, ...rest] = historianModelOverride.split("/");
152813
+ const modelID = rest.join("/");
152814
+ if (providerID && modelID) {
152815
+ const limit = getModelsDevContextLimit(providerID, modelID);
152816
+ if (typeof limit === "number" && limit > 0)
152817
+ return limit;
152818
+ }
152819
+ return DEFAULT_HISTORIAN_CONTEXT_FALLBACK;
152820
+ }
152821
+ if (typeof historianModelOverride === "string" && historianModelOverride.trim() !== "") {
152822
+ console.warn(`[magic-context] historian.model "${historianModelOverride}" lacks provider prefix ("provider/model-id"); using fallback chain for chunk-budget derivation.`);
152823
+ }
152824
+ const chain = AGENT_MODEL_REQUIREMENTS[HISTORIAN_AGENT]?.fallbackChain;
152825
+ if (!chain || chain.length === 0)
152826
+ return DEFAULT_HISTORIAN_CONTEXT_FALLBACK;
152827
+ const expanded = expandFallbackChain(chain);
152828
+ let minLimit;
152829
+ for (const key of expanded) {
152830
+ const [providerID, ...rest] = key.split("/");
152831
+ const modelID = rest.join("/");
152832
+ if (!providerID || !modelID)
152833
+ continue;
152834
+ const limit = getModelsDevContextLimit(providerID, modelID);
152835
+ if (typeof limit !== "number" || limit <= 0)
152836
+ continue;
152837
+ if (minLimit === undefined || limit < minLimit)
152838
+ minLimit = limit;
152839
+ }
152840
+ return minLimit ?? DEFAULT_HISTORIAN_CONTEXT_FALLBACK;
152841
+ }
152842
+ var TRIGGER_BUDGET_PERCENTAGE = 0.05, TRIGGER_BUDGET_MIN = 5000, TRIGGER_BUDGET_MAX = 50000, HISTORIAN_CHUNK_PERCENTAGE = 0.25, HISTORIAN_CHUNK_MIN = 8000, HISTORIAN_CHUNK_MAX = 50000, DEFAULT_HISTORIAN_CONTEXT_FALLBACK = 128000;
152843
+ var init_derive_budgets = __esm(() => {
152844
+ init_model_requirements();
152845
+ init_models_dev_cache();
152846
+ });
152847
+
152848
+ // src/features/magic-context/compaction-marker.ts
152849
+ import { Database as Database4 } from "bun:sqlite";
152850
+ import { join as join13 } from "path";
152851
+ function randomBase62(length) {
152852
+ const chars = [];
152853
+ for (let i = 0;i < length; i++) {
152854
+ chars.push(BASE62_CHARS[Math.floor(Math.random() * BASE62_CHARS.length)]);
152855
+ }
152856
+ return chars.join("");
152857
+ }
152858
+ function generateId(prefix, timestampMs, counter = 0n) {
152859
+ const encoded = BigInt(timestampMs) * 0x1000n + counter;
152860
+ const hex3 = encoded.toString(16).padStart(14, "0");
152861
+ return `${prefix}_${hex3}${randomBase62(14)}`;
152862
+ }
152863
+ function generateMessageId(timestampMs, counter = 0n) {
152864
+ return generateId("msg", timestampMs, counter);
152865
+ }
152866
+ function generatePartId(timestampMs, counter = 0n) {
152867
+ return generateId("prt", timestampMs, counter);
152868
+ }
152869
+ function getOpenCodeDbPath3() {
152870
+ return join13(getDataDir(), "opencode", "opencode.db");
152871
+ }
152872
+ function getWritableOpenCodeDb() {
152873
+ const dbPath = getOpenCodeDbPath3();
152874
+ if (cachedWriteDb?.path === dbPath) {
152875
+ return cachedWriteDb.db;
152876
+ }
152877
+ if (cachedWriteDb) {
152878
+ try {
152879
+ cachedWriteDb.db.close(false);
152880
+ } catch {}
152881
+ }
152882
+ const db = new Database4(dbPath);
152883
+ db.exec("PRAGMA journal_mode=WAL");
152884
+ db.exec("PRAGMA busy_timeout=5000");
152885
+ cachedWriteDb = { path: dbPath, db };
152886
+ return db;
152887
+ }
152888
+ function findBoundaryUserMessage(sessionId, endOrdinal) {
152889
+ const db = getWritableOpenCodeDb();
152890
+ const rows = db.prepare("SELECT id, time_created, data FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId);
152891
+ const filtered = rows.filter((row) => {
152892
+ try {
152893
+ const info = JSON.parse(row.data);
152894
+ return !(info.summary === true && info.finish === "stop");
152895
+ } catch {
152896
+ return true;
152897
+ }
152898
+ });
152899
+ let bestMatch = null;
152900
+ for (let i = 0;i < filtered.length && i < endOrdinal; i++) {
152901
+ const row = filtered[i];
152902
+ try {
152903
+ const info = JSON.parse(row.data);
152904
+ if (info.role === "user") {
152905
+ bestMatch = { id: row.id, timeCreated: row.time_created };
152906
+ }
152907
+ } catch {}
152908
+ }
152909
+ return bestMatch;
152910
+ }
152911
+ function injectCompactionMarker(args) {
152912
+ const boundary = findBoundaryUserMessage(args.sessionId, args.endOrdinal);
152913
+ if (!boundary) {
152914
+ log(`[magic-context] compaction-marker: no user message found at or before ordinal ${args.endOrdinal}`);
152915
+ return null;
152916
+ }
152917
+ const db = getWritableOpenCodeDb();
152918
+ const boundaryTime = boundary.timeCreated;
152919
+ const summaryMsgId = generateMessageId(boundaryTime + 1, 1n);
152920
+ const compactionPartId = generatePartId(boundaryTime, 1n);
152921
+ const summaryPartId = generatePartId(boundaryTime + 1, 2n);
152922
+ const summaryMsgData = JSON.stringify({
152923
+ role: "assistant",
152924
+ parentID: boundary.id,
152925
+ summary: true,
152926
+ finish: "stop",
152927
+ mode: "compaction",
152928
+ agent: "compaction",
152929
+ path: { cwd: args.directory, root: args.directory },
152930
+ cost: 0,
152931
+ tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
152932
+ modelID: "magic-context",
152933
+ providerID: "magic-context",
152934
+ time: { created: boundaryTime + 1 }
152935
+ });
152936
+ try {
152937
+ db.transaction(() => {
152938
+ db.prepare("INSERT INTO part (id, message_id, session_id, time_created, time_updated, data) VALUES (?, ?, ?, ?, ?, ?)").run(compactionPartId, boundary.id, args.sessionId, boundaryTime, boundaryTime, '{"type":"compaction","auto":true}');
152939
+ db.prepare("INSERT INTO message (id, session_id, time_created, time_updated, data) VALUES (?, ?, ?, ?, ?)").run(summaryMsgId, args.sessionId, boundaryTime + 1, boundaryTime + 1, summaryMsgData);
152940
+ db.prepare("INSERT INTO part (id, message_id, session_id, time_created, time_updated, data) VALUES (?, ?, ?, ?, ?, ?)").run(summaryPartId, summaryMsgId, args.sessionId, boundaryTime + 1, boundaryTime + 1, JSON.stringify({ type: "text", text: args.summaryText }));
152941
+ })();
152942
+ log(`[magic-context] compaction-marker: injected boundary at user msg ${boundary.id} (ordinal ~${args.endOrdinal}), summary msg ${summaryMsgId}`);
152943
+ return {
152944
+ boundaryMessageId: boundary.id,
152945
+ summaryMessageId: summaryMsgId,
152946
+ compactionPartId,
152947
+ summaryPartId
152948
+ };
152949
+ } catch (error48) {
152950
+ log(`[magic-context] compaction-marker: injection failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
152951
+ return null;
152952
+ }
152953
+ }
152954
+ function removeCompactionMarker(state) {
152955
+ try {
152956
+ const db = getWritableOpenCodeDb();
152957
+ db.transaction(() => {
152958
+ db.prepare("DELETE FROM part WHERE id = ?").run(state.summaryPartId);
152959
+ db.prepare("DELETE FROM message WHERE id = ?").run(state.summaryMessageId);
152960
+ db.prepare("DELETE FROM part WHERE id = ?").run(state.compactionPartId);
152961
+ })();
152962
+ return true;
152963
+ } catch (error48) {
152964
+ log(`[magic-context] compaction-marker: removal failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
152965
+ return false;
152966
+ }
152967
+ }
152968
+ var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", cachedWriteDb = null;
152969
+ var init_compaction_marker = __esm(() => {
152970
+ init_data_path();
152971
+ init_logger();
152972
+ });
152973
+
152974
+ // src/hooks/magic-context/compaction-marker-manager.ts
152975
+ function updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, directory) {
152976
+ const existing = getPersistedCompactionMarkerState(db, sessionId);
152977
+ if (existing) {
152978
+ if (existing.boundaryOrdinal === lastCompartmentEnd) {
152979
+ return;
152980
+ }
152981
+ try {
152982
+ removeCompactionMarker(existing);
152983
+ setPersistedCompactionMarkerState(db, sessionId, null);
152984
+ sessionLog(sessionId, `compaction-marker: removed old boundary at ordinal ${existing.boundaryOrdinal}, moving to ${lastCompartmentEnd}`);
152985
+ } catch (error48) {
152986
+ sessionLog(sessionId, `compaction-marker: failed to remove old boundary at ordinal ${existing.boundaryOrdinal}, proceeding with new injection:`, error48);
152987
+ }
152988
+ }
152989
+ const result = injectCompactionMarker({
152990
+ sessionId,
152991
+ endOrdinal: lastCompartmentEnd,
152992
+ summaryText: MARKER_SUMMARY_TEXT,
152993
+ directory: directory ?? process.cwd()
152994
+ });
152995
+ if (result) {
152996
+ setPersistedCompactionMarkerState(db, sessionId, {
152997
+ ...result,
152998
+ boundaryOrdinal: lastCompartmentEnd
152999
+ });
153000
+ sessionLog(sessionId, `compaction-marker: injected at ordinal ${lastCompartmentEnd}, boundary user msg ${result.boundaryMessageId}`);
153001
+ }
153002
+ }
153003
+ function removeCompactionMarkerForSession(db, sessionId) {
153004
+ const existing = getPersistedCompactionMarkerState(db, sessionId);
153005
+ if (existing) {
153006
+ try {
153007
+ removeCompactionMarker(existing);
153008
+ setPersistedCompactionMarkerState(db, sessionId, null);
153009
+ sessionLog(sessionId, "compaction-marker: removed on session cleanup");
153010
+ } catch (error48) {
153011
+ setPersistedCompactionMarkerState(db, sessionId, null);
153012
+ sessionLog(sessionId, "compaction-marker: removal failed during session cleanup, cleared persisted state:", error48);
153013
+ }
153014
+ }
153015
+ }
153016
+ var MARKER_SUMMARY_TEXT = "[Compacted by magic-context \u2014 session history is managed by the plugin]";
153017
+ var init_compaction_marker_manager = __esm(() => {
153018
+ init_compaction_marker();
153019
+ init_storage_meta_persisted();
153020
+ init_logger();
153021
+ });
153022
+
153023
+ // src/hooks/magic-context/note-nudger.ts
153024
+ function getPersistedNoteNudgeDeliveredAt(_db, sessionId) {
153025
+ return lastDeliveredAt.get(sessionId) ?? 0;
153026
+ }
153027
+ function recordNoteNudgeDeliveryTime(sessionId) {
153028
+ lastDeliveredAt.set(sessionId, Date.now());
153029
+ }
153030
+ function onNoteTrigger(db, sessionId, trigger) {
153031
+ setPersistedNoteNudgeTrigger(db, sessionId);
153032
+ sessionLog(sessionId, `note-nudge: trigger fired (${trigger}), triggerPending=true`);
153033
+ }
153034
+ function peekNoteNudgeText(db, sessionId, currentUserMessageId, projectIdentity) {
153035
+ const state = getPersistedNoteNudge(db, sessionId);
153036
+ if (!state.triggerPending)
153037
+ return null;
153038
+ if (!state.triggerMessageId && currentUserMessageId) {
153039
+ setPersistedNoteNudgeTriggerMessageId(db, sessionId, currentUserMessageId);
153040
+ state.triggerMessageId = currentUserMessageId;
153041
+ }
153042
+ if (state.triggerMessageId && currentUserMessageId && state.triggerMessageId === currentUserMessageId) {
153043
+ sessionLog(sessionId, `note-nudge: deferring \u2014 current user message ${currentUserMessageId} is same as trigger-time message`);
153044
+ return null;
153045
+ }
153046
+ const deliveredAt = getPersistedNoteNudgeDeliveredAt(db, sessionId);
153047
+ if (deliveredAt > 0 && Date.now() - deliveredAt < NOTE_NUDGE_COOLDOWN_MS) {
153048
+ sessionLog(sessionId, `note-nudge: suppressing \u2014 last delivered ${Math.round((Date.now() - deliveredAt) / 1000)}s ago (cooldown ${NOTE_NUDGE_COOLDOWN_MS / 60000}m)`);
153049
+ clearPersistedNoteNudge(db, sessionId);
153050
+ return null;
153051
+ }
153052
+ const notes = getSessionNotes(db, sessionId);
153053
+ const readySmartCount = projectIdentity ? getReadySmartNotes(db, projectIdentity).length : 0;
153054
+ const totalCount = notes.length + readySmartCount;
153055
+ if (totalCount === 0) {
153056
+ sessionLog(sessionId, "note-nudge: triggerPending but no notes found, skipping");
153057
+ clearPersistedNoteNudge(db, sessionId);
153058
+ return null;
153059
+ }
153060
+ const parts = [];
153061
+ if (notes.length > 0) {
153062
+ parts.push(`${notes.length} deferred note${notes.length === 1 ? "" : "s"}`);
153063
+ }
153064
+ if (readySmartCount > 0) {
153065
+ parts.push(`${readySmartCount} ready smart note${readySmartCount === 1 ? "" : "s"}`);
153066
+ }
153067
+ sessionLog(sessionId, `note-nudge: delivering nudge for ${parts.join(" and ")}`);
153068
+ return `You have ${parts.join(" and ")}. Review with ctx_note read \u2014 some may be actionable now.`;
153069
+ }
153070
+ function markNoteNudgeDelivered(db, sessionId, text, messageId) {
153071
+ setPersistedDeliveredNoteNudge(db, sessionId, messageId ? text : "", messageId ?? "");
153072
+ recordNoteNudgeDeliveryTime(sessionId);
153073
+ sessionLog(sessionId, messageId ? `note-nudge: marked delivered, sticky anchor=${messageId}` : "note-nudge: marked delivered without anchor");
153074
+ }
153075
+ function getStickyNoteNudge(db, sessionId) {
153076
+ const state = getPersistedNoteNudge(db, sessionId);
153077
+ if (!state.stickyText || !state.stickyMessageId)
153078
+ return null;
153079
+ return { text: state.stickyText, messageId: state.stickyMessageId };
153080
+ }
153081
+ function clearNoteNudgeState(db, sessionId, options) {
153082
+ if (options?.persist !== false) {
153083
+ clearPersistedNoteNudge(db, sessionId);
153084
+ }
153085
+ lastDeliveredAt.delete(sessionId);
153086
+ }
153087
+ var NOTE_NUDGE_COOLDOWN_MS, lastDeliveredAt;
153088
+ var init_note_nudger = __esm(() => {
153089
+ init_storage_meta_persisted();
153090
+ init_storage_notes();
153091
+ init_logger();
153092
+ NOTE_NUDGE_COOLDOWN_MS = 15 * 60 * 1000;
153093
+ lastDeliveredAt = new Map;
153094
+ });
153095
+
153096
+ // src/features/magic-context/memory/embedding-backfill.ts
153097
+ async function ensureMemoryEmbeddings(args) {
153098
+ if (!isEmbeddingEnabled()) {
153099
+ return args.existingEmbeddings;
153100
+ }
153101
+ const missingMemories = args.memories.filter((memory) => !args.existingEmbeddings.has(memory.id));
153102
+ if (missingMemories.length === 0) {
153103
+ return args.existingEmbeddings;
153104
+ }
153105
+ try {
153106
+ const embeddings = await embedBatch(missingMemories.map((memory) => memory.content));
153107
+ const modelId = getEmbeddingModelId();
153108
+ const staged = new Map;
153109
+ args.db.transaction(() => {
153110
+ for (const [index, memory] of missingMemories.entries()) {
153111
+ const embedding = embeddings[index];
153112
+ if (!embedding) {
153113
+ continue;
153114
+ }
153115
+ saveEmbedding(args.db, memory.id, embedding, modelId);
153116
+ staged.set(memory.id, embedding);
153117
+ }
153118
+ })();
153119
+ for (const [id, embedding] of staged) {
153120
+ args.existingEmbeddings.set(id, embedding);
153121
+ }
153122
+ } catch (error48) {
153123
+ log("[magic-context] failed to backfill memory embeddings:", error48);
153124
+ }
153125
+ return args.existingEmbeddings;
153126
+ }
153127
+ var init_embedding_backfill = __esm(() => {
153128
+ init_logger();
153129
+ init_embedding();
153130
+ init_storage_memory_embeddings();
153131
+ });
153132
+
153133
+ // src/features/magic-context/memory/promotion.ts
153134
+ function isPromotableCategory(category) {
153135
+ return PROMOTABLE_CATEGORIES.some((promotableCategory) => promotableCategory === category);
153136
+ }
153137
+ function resolveExpiresAt(category) {
153138
+ const ttl = CATEGORY_DEFAULT_TTL[category];
153139
+ return ttl === undefined ? null : Date.now() + ttl;
153140
+ }
153141
+ function promoteSessionFactsToMemory(db, sessionId, projectPath, facts) {
153142
+ for (const fact of facts) {
153143
+ if (!isPromotableCategory(fact.category)) {
153144
+ continue;
153145
+ }
153146
+ try {
153147
+ const normalizedHash = computeNormalizedHash(fact.content);
153148
+ const existingMemory = getMemoryByHash(db, projectPath, fact.category, normalizedHash);
153149
+ if (existingMemory) {
153150
+ updateMemorySeenCount(db, existingMemory.id);
153151
+ continue;
153152
+ }
153153
+ const memoryInput = {
153154
+ projectPath,
153155
+ category: fact.category,
153156
+ content: fact.content,
153157
+ sourceSessionId: sessionId,
153158
+ sourceType: "historian",
153159
+ expiresAt: resolveExpiresAt(fact.category)
153160
+ };
153161
+ const memory = insertMemory(db, memoryInput);
153162
+ embedAndStoreMemory(db, sessionId, memory.id, memory.content);
153163
+ } catch (error48) {
153164
+ sessionLog(sessionId, `memory promotion failed for fact "${fact.content.slice(0, 60)}":`, error48);
153165
+ }
153166
+ }
153167
+ }
153168
+ async function embedAndStoreMemory(db, sessionId, memoryId, content) {
153169
+ try {
153170
+ const embedding = await embedText(content);
153171
+ if (embedding) {
153172
+ saveEmbedding(db, memoryId, embedding, getEmbeddingModelId());
153173
+ }
153174
+ } catch (error48) {
153175
+ sessionLog(sessionId, `memory embedding failed for memory ${memoryId}:`, error48);
153176
+ }
153177
+ }
153178
+ var init_promotion = __esm(() => {
153179
+ init_logger();
153180
+ init_constants();
153181
+ init_embedding();
153182
+ init_storage_memory();
153183
+ init_storage_memory_embeddings();
153184
+ });
153185
+
153186
+ // src/features/magic-context/memory/storage-memory-fts.ts
153187
+ function getSearchStatement(db) {
153188
+ let stmt = searchStatements.get(db);
153189
+ if (!stmt) {
153190
+ const selectColumns = Object.entries(COLUMN_MAP).map(([property, column]) => `memories.${column} AS ${property}`).join(", ");
153191
+ stmt = db.prepare(`SELECT ${selectColumns} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path = ? AND memories.status IN ('active', 'permanent') AND (memories.expires_at IS NULL OR memories.expires_at > ?) AND memories_fts MATCH ? ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`);
153192
+ searchStatements.set(db, stmt);
153193
+ }
153194
+ return stmt;
153195
+ }
153196
+ function sanitizeFtsQuery(query) {
153197
+ const tokens = query.split(/\s+/).filter((token) => token.length > 0);
153198
+ if (tokens.length === 0)
153199
+ return "";
153200
+ return tokens.map((token) => `"${token.replace(/"/g, '""')}"`).join(" ");
153201
+ }
153202
+ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT) {
153203
+ const trimmedQuery = query.trim();
153204
+ if (trimmedQuery.length === 0 || limit <= 0) {
153205
+ return [];
153206
+ }
153207
+ const sanitized = sanitizeFtsQuery(trimmedQuery);
153208
+ if (sanitized.length === 0) {
153209
+ return [];
153210
+ }
153211
+ const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
153212
+ return rows.map(toMemory);
153213
+ }
153214
+ var DEFAULT_SEARCH_LIMIT = 10, searchStatements;
153215
+ var init_storage_memory_fts = __esm(() => {
153216
+ init_storage_memory();
153217
+ searchStatements = new WeakMap;
153218
+ });
153219
+ // src/features/magic-context/memory/index.ts
153220
+ var init_memory = __esm(() => {
153221
+ init_project_identity();
153222
+ init_promotion();
153223
+ init_constants();
153224
+ init_embedding();
153225
+ init_embedding_backfill();
153226
+ init_embedding_cache();
153227
+ init_storage_memory();
153228
+ init_storage_memory_embeddings();
153229
+ init_storage_memory_fts();
153230
+ });
153231
+
153232
+ // src/hooks/magic-context/caveman.ts
153233
+ function protectRegions(text) {
153234
+ const preserved = [];
153235
+ let working = text;
153236
+ for (const pattern of PRESERVATION_PATTERNS) {
153237
+ working = working.replace(pattern, (match) => {
153238
+ const placeholder = `\x00MC_PRES_${preserved.length}\x00`;
153239
+ preserved.push({ placeholder, original: match });
153240
+ return placeholder;
153241
+ });
153242
+ }
153243
+ return { text: working, preserved };
153244
+ }
153245
+ function restoreRegions(text, preserved) {
153246
+ let working = text;
153247
+ for (let i = preserved.length - 1;i >= 0; i--) {
153248
+ working = working.split(preserved[i].placeholder).join(preserved[i].original);
153249
+ }
153250
+ return working;
153251
+ }
153252
+ function buildPhraseDropRegex(phrases) {
153253
+ const escaped = phrases.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
153254
+ return new RegExp(`(\\s+)?\\b(?:${escaped.join("|")})\\b`, "gi");
153255
+ }
153256
+ function dropPhrases(text, phrases) {
153257
+ return text.replace(buildPhraseDropRegex(phrases), "");
153258
+ }
153259
+ function dropArticles(text) {
153260
+ let working = text.replace(/\b(?:the|a|an)\b\s+/gi, "");
153261
+ working = working.replace(/ +/g, " ");
153262
+ return working;
153263
+ }
153264
+ function dropAuxiliaries(text) {
153265
+ const sorted = [...AUXILIARIES].sort((a, b) => b.length - a.length);
153266
+ const escaped = sorted.map((a) => a.replace(/\s+/g, "\\s+"));
153267
+ const pattern = new RegExp(`\\s+\\b(?:${escaped.join("|")})\\b\\s+(?=\\w+(?:ed|en|ing|ized|ised)\\b)`, "gi");
153268
+ let working = text.replace(pattern, " ");
153269
+ working = working.replace(/ +/g, " ");
153270
+ return working;
153271
+ }
153272
+ function applyPhraseShortenings(text) {
153273
+ let working = text;
153274
+ for (const [pattern, replacement] of PHRASE_SHORTENINGS) {
153275
+ working = working.replace(pattern, replacement);
153276
+ }
153277
+ return working;
153278
+ }
153279
+ function applyUltraConnectives(text) {
153280
+ let working = text;
153281
+ for (const [pattern, replacement] of ULTRA_CONNECTIVE_REPLACEMENTS) {
153282
+ working = working.replace(pattern, replacement);
153283
+ }
153284
+ return working;
153285
+ }
153286
+ function countWordOccurrences(text, term) {
153287
+ const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
153288
+ const matches = text.match(new RegExp(`\\b${escaped}\\b`, "gi"));
153289
+ return matches ? matches.length : 0;
153290
+ }
153291
+ function applyUltraAbbreviations(text) {
153292
+ let working = text;
153293
+ for (const [term, abbreviation] of Object.entries(ULTRA_ABBREVIATIONS)) {
153294
+ if (countWordOccurrences(working, term) < 3)
153295
+ continue;
153296
+ const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
153297
+ working = working.replace(new RegExp(`\\b${escaped}\\b`, "gi"), (match) => {
153298
+ return match[0] === match[0].toUpperCase() ? abbreviation[0].toUpperCase() + abbreviation.slice(1) : abbreviation;
153299
+ });
153300
+ }
153301
+ return working;
153302
+ }
153303
+ function transformPreservingUserLines(text, transform2) {
153304
+ const lines = text.split(`
153305
+ `);
153306
+ const output = [];
153307
+ let buffer2 = [];
153308
+ const flushBuffer = () => {
153309
+ if (buffer2.length === 0)
153310
+ return;
153311
+ const joined = buffer2.join(`
153312
+ `);
153313
+ output.push(transform2(joined));
153314
+ buffer2 = [];
153315
+ };
153316
+ for (const line of lines) {
153317
+ if (line.startsWith("U: ")) {
153318
+ flushBuffer();
153319
+ output.push(line);
153320
+ } else {
153321
+ buffer2.push(line);
153322
+ }
153323
+ }
153324
+ flushBuffer();
153325
+ return output.join(`
153326
+ `);
153327
+ }
153328
+ function normalizeWhitespace(text) {
153329
+ return text.split(`
153330
+ `).map((line) => line.replace(/[ \t]+/g, " ").replace(/[ \t]+$/, "")).join(`
153331
+ `).replace(/\n{3,}/g, `
153332
+
153333
+ `);
153334
+ }
153335
+ function cavemanCompress(text, level) {
153336
+ if (text.length === 0)
153337
+ return text;
153338
+ const { text: protectedText, preserved } = protectRegions(text);
153339
+ const transformed = transformPreservingUserLines(protectedText, (chunk) => {
153340
+ let working = chunk;
153341
+ working = dropPhrases(working, FILLER_WORDS);
153342
+ working = dropPhrases(working, HEDGING_PHRASES);
153343
+ working = dropPhrases(working, PLEASANTRIES);
153344
+ working = applyPhraseShortenings(working);
153345
+ if (level === "full" || level === "ultra") {
153346
+ working = dropAuxiliaries(working);
153347
+ working = dropArticles(working);
153348
+ }
153349
+ if (level === "ultra") {
153350
+ working = applyUltraConnectives(working);
153351
+ working = applyUltraAbbreviations(working);
153352
+ }
153353
+ return working;
153354
+ });
153355
+ const restored = restoreRegions(transformed, preserved);
153356
+ return normalizeWhitespace(restored).trim();
153357
+ }
153358
+ var PRESERVATION_PATTERNS, FILLER_WORDS, HEDGING_PHRASES, PLEASANTRIES, AUXILIARIES, PHRASE_SHORTENINGS, ULTRA_CONNECTIVE_REPLACEMENTS, ULTRA_ABBREVIATIONS;
153359
+ var init_caveman = __esm(() => {
153360
+ PRESERVATION_PATTERNS = [
153361
+ /```[\s\S]*?```/g,
153362
+ /`[^`\n]+`/g,
153363
+ /https?:\/\/\S+/g,
153364
+ /\u00A7\d+\u00A7/g,
153365
+ /\b(?:msg|ses|toolu)_[A-Za-z0-9]+/g,
153366
+ /(?:\.{1,2}\/)?(?:[\w.-]+\/)+[\w.-]+\.\w{1,6}/g,
153367
+ /(?<![a-z0-9])[0-9a-f]{7,40}(?![a-z0-9])/gi
153368
+ ];
153369
+ FILLER_WORDS = [
153370
+ "just",
153371
+ "really",
153372
+ "basically",
153373
+ "actually",
153374
+ "essentially",
153375
+ "simply",
153376
+ "clearly",
153377
+ "obviously",
153378
+ "quite",
153379
+ "very",
153380
+ "somewhat",
153381
+ "rather",
153382
+ "fairly",
153383
+ "sort of",
153384
+ "kind of",
153385
+ "a bit"
153386
+ ];
153387
+ HEDGING_PHRASES = [
153388
+ "i think",
153389
+ "i believe",
153390
+ "i feel",
153391
+ "probably",
153392
+ "perhaps",
153393
+ "maybe",
153394
+ "it seems",
153395
+ "it appears",
153396
+ "arguably",
153397
+ "i suppose",
153398
+ "i guess"
153399
+ ];
153400
+ PLEASANTRIES = ["please", "thanks", "thank you", "kindly", "if possible"];
153401
+ AUXILIARIES = [
153402
+ "was",
153403
+ "were",
153404
+ "is",
153405
+ "are",
153406
+ "am",
153407
+ "be",
153408
+ "been",
153409
+ "being",
153410
+ "has been",
153411
+ "had been",
153412
+ "have been",
153413
+ "will be",
153414
+ "would be",
153415
+ "could be",
153416
+ "should be",
153417
+ "might be",
153418
+ "may be"
153419
+ ];
153420
+ PHRASE_SHORTENINGS = [
153421
+ [/\bin order to\b/gi, "to"],
153422
+ [/\bdue to the fact that\b/gi, "because"],
153423
+ [/\bat this point in time\b/gi, "now"],
153424
+ [/\bat the moment\b/gi, "now"],
153425
+ [/\bin the event that\b/gi, "if"],
153426
+ [/\bfor the purpose of\b/gi, "for"],
153427
+ [/\bwith regard to\b/gi, "about"],
153428
+ [/\bin spite of the fact that\b/gi, "though"],
153429
+ [/\bon the grounds that\b/gi, "because"],
153430
+ [/\bfor the reason that\b/gi, "because"]
153431
+ ];
153432
+ ULTRA_CONNECTIVE_REPLACEMENTS = [
153433
+ [/\b(?:and then|then after|afterwards)\b/gi, "\u2192"],
153434
+ [/\bbecause of\b/gi, "//"],
153435
+ [/\btherefore\b/gi, "\u2192"],
153436
+ [/\bbecause\b/gi, "//"],
153437
+ [/\bhowever\b/gi, "but"],
153438
+ [/\bfurthermore\b/gi, "+"],
153439
+ [/\badditionally\b/gi, "+"],
153440
+ [/\bas well as\b/gi, "+"],
153441
+ [/ and /gi, " + "],
153442
+ [/ or /gi, " | "]
153443
+ ];
153444
+ ULTRA_ABBREVIATIONS = {
153445
+ historian: "hist",
153446
+ compartment: "cmpt",
153447
+ compartments: "cmpts",
153448
+ compressor: "cmp",
153449
+ compression: "cmp",
153450
+ context: "ctx",
153451
+ message: "msg",
153452
+ messages: "msgs",
153453
+ session: "ses",
153454
+ configuration: "cfg",
153455
+ config: "cfg",
153456
+ implementation: "impl",
153457
+ implemented: "impl",
153458
+ repository: "repo",
153459
+ database: "db",
153460
+ directory: "dir"
153461
+ };
153462
+ });
153463
+
153464
+ // src/hooks/magic-context/compartment-runner-compressor.ts
153465
+ function cavemanLevelForDepth(outputDepth) {
153466
+ if (outputDepth <= 1)
153467
+ return null;
153468
+ if (outputDepth === 2)
153469
+ return "lite";
153470
+ if (outputDepth === 3)
153471
+ return "full";
153472
+ if (outputDepth === 4)
153473
+ return "ultra";
153474
+ return null;
153475
+ }
153476
+ async function runCompressionPassIfNeeded(deps) {
153477
+ const { db, sessionId, historyBudgetTokens } = deps;
153478
+ const minCompartmentRatio = deps.minCompartmentRatio ?? DEFAULT_COMPRESSOR_MIN_COMPARTMENT_RATIO;
153479
+ const maxMergeDepth = deps.maxMergeDepth ?? DEFAULT_COMPRESSOR_MAX_MERGE_DEPTH;
153480
+ const compartments = getCompartments(db, sessionId);
153481
+ if (compartments.length <= 1)
153482
+ return false;
153483
+ const facts = getSessionFacts(db, sessionId);
153484
+ let totalTokens = 0;
153485
+ for (const c of compartments) {
153486
+ totalTokens += estimateTokens(`<compartment start="${c.startMessage}" end="${c.endMessage}" title="${c.title}">
153487
+ ${c.content}
153488
+ </compartment>
153489
+ `);
153490
+ }
153491
+ for (const f of facts) {
153492
+ totalTokens += estimateTokens(`* ${f.content}
153493
+ `);
153494
+ }
153495
+ if (totalTokens <= historyBudgetTokens) {
153496
+ sessionLog(sessionId, `compressor: history block ~${totalTokens} tokens within budget ${historyBudgetTokens}, skipping`);
153497
+ return false;
153498
+ }
153499
+ const lastEndMessage = compartments[compartments.length - 1].endMessage;
153500
+ const floor = Math.max(1, Math.ceil(lastEndMessage / minCompartmentRatio));
153501
+ if (compartments.length <= floor) {
153502
+ sessionLog(sessionId, `compressor: at floor (${compartments.length} compartments, floor=${floor} from ${lastEndMessage} msgs), skipping`);
153503
+ return false;
153504
+ }
153505
+ const overage = totalTokens - historyBudgetTokens;
153506
+ sessionLog(sessionId, `compressor: history block ~${totalTokens} tokens exceeds budget ${historyBudgetTokens} by ~${overage} tokens`);
153507
+ const maxCompartmentsPerPass = deps.maxCompartmentsPerPass ?? DEFAULT_COMPRESSOR_MAX_COMPARTMENTS_PER_PASS;
153508
+ const graceCompartments = deps.graceCompartments ?? DEFAULT_COMPRESSOR_GRACE_COMPARTMENTS;
153509
+ const scored = scoreCompartments(db, sessionId, compartments);
153510
+ const floorHeadroom = compartments.length - floor;
153511
+ if (floorHeadroom < 1) {
153512
+ sessionLog(sessionId, `compressor: no floor headroom (${compartments.length} compartments, floor=${floor}), skipping`);
153513
+ return false;
153514
+ }
153515
+ const contiguous = findOldestContiguousSameDepthBand(scored, {
153516
+ maxPickable: maxCompartmentsPerPass,
153517
+ maxMergeDepth,
153518
+ graceCompartments,
153519
+ floorHeadroom
153520
+ });
153521
+ if (contiguous.length < 2) {
153522
+ sessionLog(sessionId, `compressor: no eligible same-depth band found (floor=${floor}, maxDepth=${maxMergeDepth}, grace=${graceCompartments}, maxPerPass=${maxCompartmentsPerPass}), skipping`);
153523
+ return false;
153524
+ }
153525
+ const firstIndex = contiguous[0].index;
153526
+ const lastIndex = contiguous[contiguous.length - 1].index;
153527
+ const selectedCompartments = contiguous.map((s) => s.compartment);
153528
+ const selectedTokens = contiguous.reduce((t, s) => t + s.tokenEstimate, 0);
153529
+ const overallAverageDepth = contiguous.reduce((sum, s) => sum + s.averageDepth, 0) / contiguous.length;
153530
+ const outputDepth = Math.min(5, Math.max(1, Math.round(overallAverageDepth) + 1));
153531
+ const mergeRatio = COMPRESSOR_MERGE_RATIO_BY_DEPTH[outputDepth] ?? 2;
153532
+ const outputCount = mergeRatio > 0 ? Math.max(1, Math.ceil(contiguous.length / mergeRatio)) : 1;
153533
+ sessionLog(sessionId, `compressor: scored ${compartments.length}, picked ${contiguous.length} contiguous (${selectedCompartments[0].startMessage}-${selectedCompartments[selectedCompartments.length - 1].endMessage}, ~${selectedTokens} tokens), avg_depth=${overallAverageDepth.toFixed(1)} \u2192 output_depth=${outputDepth} (ratio=${mergeRatio}, target=${outputCount} compartments)`);
153534
+ if (outputDepth === 5) {
153535
+ return finalizeCompression({
153536
+ db,
153537
+ sessionId,
153538
+ compartments,
153539
+ leadingCount: firstIndex,
153540
+ trailingIndex: lastIndex + 1,
153541
+ selectedCompartments,
153542
+ compressed: selectedCompartments.map((c) => ({
153543
+ startMessage: c.startMessage,
153544
+ endMessage: c.endMessage,
153545
+ startMessageId: c.startMessageId,
153546
+ endMessageId: c.endMessageId,
153547
+ title: c.title,
153548
+ content: ""
153549
+ })),
153550
+ originalStart: selectedCompartments[0].startMessage,
153551
+ originalEnd: selectedCompartments[selectedCompartments.length - 1].endMessage,
153552
+ facts,
153553
+ logLabel: `depth-5 title-only collapse (${selectedCompartments.length} \u2192 ${selectedCompartments.length})`
153554
+ });
153555
+ }
153556
+ try {
153557
+ const targetTokens = Math.max(200, Math.floor(selectedTokens / mergeRatio));
153558
+ const llmCompressed = await runCompressorPass({
153559
+ ...deps,
153560
+ compartments: selectedCompartments,
153561
+ currentTokens: selectedTokens,
153562
+ targetTokens,
153563
+ outputCount,
153564
+ outputDepth
153565
+ });
153566
+ if (!llmCompressed) {
153567
+ sessionLog(sessionId, "compressor: LLM pass failed, keeping existing compartments");
153568
+ return false;
153569
+ }
153570
+ const level = cavemanLevelForDepth(outputDepth);
153571
+ const finalCompressed = level ? llmCompressed.map((c) => ({ ...c, content: cavemanCompress(c.content, level) })) : llmCompressed;
153572
+ return finalizeCompression({
153573
+ db,
153574
+ sessionId,
153575
+ compartments,
153576
+ leadingCount: firstIndex,
153577
+ trailingIndex: lastIndex + 1,
153578
+ selectedCompartments,
153579
+ compressed: finalCompressed,
153580
+ originalStart: selectedCompartments[0].startMessage,
153581
+ originalEnd: selectedCompartments[selectedCompartments.length - 1].endMessage,
153582
+ facts,
153583
+ logLabel: `depth-${outputDepth} (${selectedCompartments.length} \u2192 ${finalCompressed.length})`
153584
+ });
153585
+ } catch (error48) {
153586
+ sessionLog(sessionId, "compressor: unexpected error:", getErrorMessage(error48));
153587
+ return false;
153588
+ }
153589
+ }
153590
+ function scoreCompartments(db, sessionId, compartments) {
153591
+ let maxDepthAcrossSession = 0;
153592
+ for (const c of compartments) {
153593
+ const d = getAverageCompressionDepth(db, sessionId, c.startMessage, c.endMessage);
153594
+ if (d > maxDepthAcrossSession)
153595
+ maxDepthAcrossSession = d;
153596
+ }
153597
+ return compartments.map((compartment, index) => {
153598
+ const tokenEstimate = estimateTokens(`<compartment start="${compartment.startMessage}" end="${compartment.endMessage}" title="${compartment.title}">
153599
+ ${compartment.content}
153600
+ </compartment>
153601
+ `);
153602
+ const averageDepth = getAverageCompressionDepth(db, sessionId, compartment.startMessage, compartment.endMessage);
153603
+ const normalizedAge = compartments.length > 1 ? 1 - index / (compartments.length - 1) : 1;
153604
+ const normalizedDepth = maxDepthAcrossSession > 0 ? 1 - averageDepth / maxDepthAcrossSession : 1;
153605
+ const score = 0.7 * normalizedAge + 0.3 * normalizedDepth;
153606
+ return { compartment, index, tokenEstimate, averageDepth, score };
153607
+ });
153608
+ }
153609
+ function findOldestContiguousSameDepthBand(scored, constraints) {
153610
+ const { maxPickable, maxMergeDepth, graceCompartments, floorHeadroom } = constraints;
153611
+ const hardMaxPick = Math.max(0, Math.min(maxPickable, floorHeadroom));
153612
+ if (hardMaxPick < 2)
153613
+ return [];
153614
+ const scanEnd = Math.max(0, scored.length - graceCompartments);
153615
+ if (scanEnd < 2)
153616
+ return [];
153617
+ let i = 0;
153618
+ while (i < scanEnd) {
153619
+ const c = scored[i];
153620
+ if (!c || c.averageDepth >= maxMergeDepth) {
153621
+ i++;
153622
+ continue;
153623
+ }
153624
+ const anchorDepth = Math.round(c.averageDepth);
153625
+ let j = i;
153626
+ while (j < scanEnd) {
153627
+ const entry = scored[j];
153628
+ if (!entry)
153629
+ break;
153630
+ if (entry.averageDepth >= maxMergeDepth)
153631
+ break;
153632
+ if (Math.round(entry.averageDepth) !== anchorDepth)
153633
+ break;
153634
+ if (j - i >= hardMaxPick)
153635
+ break;
153636
+ j++;
153637
+ }
153638
+ const runLen = j - i;
153639
+ if (runLen >= 2) {
153640
+ return scored.slice(i, j);
153641
+ }
153642
+ i = Math.max(j, i + 1);
153643
+ }
153644
+ return [];
153645
+ }
153646
+ function snapLLMOutputToInputBoundaries(llmOutput, inputCompartments) {
153647
+ const sorted = [...inputCompartments].sort((a, b) => a.startMessage - b.startMessage);
153648
+ const containing = (ord) => {
153649
+ let lo = 0;
153650
+ let hi = sorted.length - 1;
153651
+ while (lo <= hi) {
153652
+ const mid = lo + hi >> 1;
153653
+ const c = sorted[mid];
153654
+ if (!c)
153655
+ return null;
153656
+ if (ord < c.startMessage)
153657
+ hi = mid - 1;
153658
+ else if (ord > c.endMessage)
153659
+ lo = mid + 1;
153660
+ else
153661
+ return c;
153662
+ }
153663
+ return null;
153664
+ };
153665
+ const result = [];
153666
+ let snapCount = 0;
153667
+ for (const pc of llmOutput) {
153668
+ const startOwner = containing(pc.startMessage);
153669
+ const endOwner = containing(pc.endMessage);
153670
+ if (!startOwner || !endOwner) {
153671
+ return null;
153672
+ }
153673
+ if (startOwner.startMessage !== pc.startMessage)
153674
+ snapCount++;
153675
+ if (endOwner.endMessage !== pc.endMessage)
153676
+ snapCount++;
153677
+ result.push({
153678
+ startMessage: startOwner.startMessage,
153679
+ endMessage: endOwner.endMessage,
153680
+ startMessageId: startOwner.startMessageId,
153681
+ endMessageId: endOwner.endMessageId,
153682
+ title: pc.title,
153683
+ content: pc.content
153684
+ });
153685
+ }
153686
+ return { result, snapCount };
153687
+ }
153688
+ function finalizeCompression(args) {
153689
+ const {
153690
+ db,
153691
+ sessionId,
153692
+ compartments,
153693
+ leadingCount,
153694
+ trailingIndex,
153695
+ selectedCompartments: _selectedCompartments,
153696
+ compressed,
153697
+ originalStart,
153698
+ originalEnd,
153699
+ facts,
153700
+ logLabel
153701
+ } = args;
153702
+ const compressedStart = compressed[0].startMessage;
153703
+ const compressedEnd = compressed[compressed.length - 1].endMessage;
153704
+ if (compressedStart !== originalStart || compressedEnd !== originalEnd) {
153705
+ sessionLog(sessionId, `compressor: compressed range ${compressedStart}-${compressedEnd} doesn't match original ${originalStart}-${originalEnd}, aborting`);
153706
+ return false;
153707
+ }
153708
+ for (let i = 1;i < compressed.length; i++) {
153709
+ const prev = compressed[i - 1];
153710
+ const curr = compressed[i];
153711
+ if (curr.startMessage <= prev.endMessage) {
153712
+ sessionLog(sessionId, `compressor: overlap at compartment ${i}, aborting`);
153713
+ return false;
153714
+ }
153715
+ if (curr.startMessage > prev.endMessage + 1) {
153716
+ sessionLog(sessionId, `compressor: gap at compartment ${i}, aborting`);
153717
+ return false;
153718
+ }
153719
+ }
153720
+ const leading = compartments.slice(0, leadingCount);
153721
+ const trailing = compartments.slice(trailingIndex);
153722
+ const allCompartments = [
153723
+ ...leading.map((c, i) => ({
153724
+ sequence: i,
153725
+ startMessage: c.startMessage,
153726
+ endMessage: c.endMessage,
153727
+ startMessageId: c.startMessageId,
153728
+ endMessageId: c.endMessageId,
153729
+ title: c.title,
153730
+ content: c.content
153731
+ })),
153732
+ ...compressed.map((c, i) => ({
153733
+ sequence: leading.length + i,
153734
+ startMessage: c.startMessage,
153735
+ endMessage: c.endMessage,
153736
+ startMessageId: c.startMessageId,
153737
+ endMessageId: c.endMessageId,
153738
+ title: c.title,
153739
+ content: c.content
153740
+ })),
153741
+ ...trailing.map((c, i) => ({
153742
+ sequence: leading.length + compressed.length + i,
153743
+ startMessage: c.startMessage,
153744
+ endMessage: c.endMessage,
153745
+ startMessageId: c.startMessageId,
153746
+ endMessageId: c.endMessageId,
153747
+ title: c.title,
153748
+ content: c.content
153749
+ }))
153750
+ ];
153751
+ replaceAllCompartmentState(db, sessionId, allCompartments, facts.map((f) => ({ category: f.category, content: f.content })));
153752
+ incrementCompressionDepth(db, sessionId, originalStart, originalEnd);
153753
+ sessionLog(sessionId, `compressor: completed ${logLabel}`);
153754
+ return true;
153755
+ }
153756
+ async function runCompressorPass(args) {
153757
+ const {
153758
+ client,
153759
+ sessionId,
153760
+ directory,
153761
+ compartments,
153762
+ currentTokens,
153763
+ targetTokens,
153764
+ outputCount,
153765
+ outputDepth,
153766
+ historianTimeoutMs
153767
+ } = args;
153768
+ const prompt = buildCompressorPrompt(compartments, currentTokens, targetTokens, outputDepth, outputCount);
153769
+ let agentSessionId = null;
153770
+ try {
153771
+ const createResponse = await client.session.create({
153772
+ body: { parentID: sessionId, title: "magic-context-compressor" },
153773
+ query: { directory }
153774
+ });
153775
+ const createdSession = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
153776
+ agentSessionId = typeof createdSession?.id === "string" ? createdSession.id : null;
153777
+ if (!agentSessionId) {
153778
+ sessionLog(sessionId, "compressor: could not create child session");
153779
+ return null;
153780
+ }
153781
+ await promptSyncWithModelSuggestionRetry(client, {
153782
+ path: { id: agentSessionId },
153783
+ query: { directory },
153784
+ body: {
153785
+ agent: HISTORIAN_AGENT2,
153786
+ parts: [{ type: "text", text: prompt }]
153787
+ }
153788
+ }, { timeoutMs: historianTimeoutMs ?? DEFAULT_HISTORIAN_TIMEOUT_MS });
153789
+ const messagesResponse = await client.session.messages({
153790
+ path: { id: agentSessionId },
153791
+ query: { directory }
153792
+ });
153793
+ const messages = normalizeSDKResponse(messagesResponse, [], {
153794
+ preferResponseOnMissingData: true
153795
+ });
153796
+ const result = extractLatestAssistantText(messages);
153797
+ if (!result) {
153798
+ sessionLog(sessionId, "compressor: historian returned no output");
153799
+ return null;
153800
+ }
153801
+ const parsed = parseCompartmentOutput(result);
153802
+ if (parsed.compartments.length === 0) {
153803
+ sessionLog(sessionId, "compressor: historian returned no compartments");
153804
+ return null;
153805
+ }
153806
+ const snapped = snapLLMOutputToInputBoundaries(parsed.compartments, compartments);
153807
+ if (!snapped) {
153808
+ sessionLog(sessionId, "compressor: rejecting \u2014 LLM output contains ordinal(s) outside input range");
153809
+ return null;
153810
+ }
153811
+ if (snapped.snapCount > 0) {
153812
+ sessionLog(sessionId, `compressor: snapped ${snapped.snapCount} LLM boundary value(s) to input compartment boundaries`);
153813
+ }
153814
+ return snapped.result;
153815
+ } catch (error48) {
153816
+ sessionLog(sessionId, "compressor: historian call failed:", getErrorMessage(error48));
153817
+ return null;
153818
+ } finally {
153819
+ if (agentSessionId) {
153820
+ await client.session.delete({ path: { id: agentSessionId }, query: { directory } }).catch((e) => {
153821
+ sessionLog(sessionId, "compressor: session cleanup failed:", getErrorMessage(e));
153822
+ });
153823
+ }
153824
+ }
153825
+ }
153826
+ var HISTORIAN_AGENT2 = "historian";
153827
+ var init_compartment_runner_compressor = __esm(() => {
153828
+ init_magic_context();
153829
+ init_storage();
153830
+ init_shared();
153831
+ init_assistant_message_extractor();
153832
+ init_logger();
153833
+ init_caveman();
153834
+ init_compartment_parser();
153835
+ init_compartment_prompt();
153836
+ init_read_session_formatting();
153837
+ });
153838
+
153839
+ // src/hooks/magic-context/compartment-runner-drop-queue.ts
153840
+ function queueDropsForCompartmentalizedMessages(db, sessionId, upToMessageIndex) {
153841
+ const tags = getTagsBySession(db, sessionId);
153842
+ const rawTagKeys = new Set(getRawSessionTagKeysThrough(sessionId, upToMessageIndex));
153843
+ let dropsQueued = 0;
153844
+ for (const tag of tags) {
153845
+ if (tag.status !== "active")
153846
+ continue;
153847
+ if (rawTagKeys.has(tag.messageId)) {
153848
+ queuePendingOp(db, sessionId, tag.tagNumber, "drop");
153849
+ dropsQueued += 1;
153850
+ }
153851
+ }
153852
+ sessionLog(sessionId, `compartment agent: queued ${dropsQueued} drops for messages 0-${upToMessageIndex}`);
153853
+ }
153854
+ var init_compartment_runner_drop_queue = __esm(() => {
153855
+ init_storage_ops();
153856
+ init_storage_tags();
153857
+ init_logger();
153858
+ init_read_session_chunk();
153859
+ });
153860
+
152765
153861
  // src/hooks/magic-context/compartment-runner-incremental.ts
152766
153862
  function shouldSuppressHistorianAlert(sessionId) {
152767
153863
  const lastAlert = lastHistorianAlertBySession.get(sessionId);
@@ -152836,6 +153932,8 @@ ${chunk.text}`);
152836
153932
  const parentSessionResponse = await client.session.get({ path: { id: sessionId } }).catch(() => null);
152837
153933
  const parentSession = normalizeSDKResponse(parentSessionResponse, null, { preferResponseOnMissingData: true });
152838
153934
  const sessionDirectory = parentSession?.directory ?? directory;
153935
+ const maxExistingSequence = priorCompartments.reduce((max, c) => c.sequence > max ? c.sequence : max, -1);
153936
+ const sequenceOffset = priorCompartments.length === 0 ? 0 : maxExistingSequence + 1;
152839
153937
  const validatedPass = await runValidatedHistorianPass({
152840
153938
  client,
152841
153939
  parentSessionId: sessionId,
@@ -152843,10 +153941,11 @@ ${chunk.text}`);
152843
153941
  prompt,
152844
153942
  chunk,
152845
153943
  priorCompartments,
152846
- sequenceOffset: priorCompartments.length,
153944
+ sequenceOffset,
152847
153945
  dumpLabelBase: `incremental-${sessionId}-${chunk.startIndex}-${chunk.endIndex}`,
152848
153946
  timeoutMs: historianTimeoutMs,
152849
- fallbackModelId: deps.fallbackModelId
153947
+ fallbackModelId: deps.fallbackModelId,
153948
+ twoPass: deps.historianTwoPass
152850
153949
  });
152851
153950
  if (!validatedPass.ok) {
152852
153951
  incrementHistorianFailure(db, sessionId, validatedPass.error);
@@ -152888,7 +153987,9 @@ No new compartments or facts were written. Check the historian model/output and
152888
153987
  sessionId,
152889
153988
  directory: sessionDirectory,
152890
153989
  historyBudgetTokens: deps.historyBudgetTokens,
152891
- historianTimeoutMs
153990
+ historianTimeoutMs,
153991
+ minCompartmentRatio: deps.compressorMinCompartmentRatio,
153992
+ maxMergeDepth: deps.compressorMaxMergeDepth
152892
153993
  });
152893
153994
  }
152894
153995
  updateSessionMeta(db, sessionId, { compartmentInProgress: false });
@@ -152972,6 +154073,7 @@ async function executeContextRecompInternal(deps) {
152972
154073
  const promoted2 = promoteRecompStaging(db, sessionId);
152973
154074
  if (!promoted2)
152974
154075
  return null;
154076
+ clearCompressionDepth(db, sessionId);
152975
154077
  clearInjectionCache(sessionId);
152976
154078
  if (deps.directory) {
152977
154079
  promoteSessionFactsToMemory(db, sessionId, resolveProjectIdentity(deps.directory), promoted2.facts);
@@ -153063,6 +154165,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
153063
154165
  dumpLabelBase: `recomp-${sessionId}-${chunk.startIndex}-${chunk.endIndex}-pass-${passCount + 1}`,
153064
154166
  timeoutMs: historianTimeoutMs,
153065
154167
  fallbackModelId: deps.fallbackModelId,
154168
+ twoPass: deps.historianTwoPass,
153066
154169
  callbacks: {
153067
154170
  onRepairRetry: async (error48) => {
153068
154171
  await sendIgnoredMessage(client, sessionId, `## Magic Recomp
@@ -153138,6 +154241,7 @@ Nothing was written.`;
153138
154241
  replaceAllCompartmentState(db, sessionId, candidateCompartments, candidateFacts);
153139
154242
  clearRecompStaging(db, sessionId);
153140
154243
  }
154244
+ clearCompressionDepth(db, sessionId);
153141
154245
  clearInjectionCache(sessionId);
153142
154246
  const finalCompartments = promoted?.compartments ?? candidateCompartments;
153143
154247
  const finalFacts = promoted?.facts ?? candidateFacts;
@@ -153156,7 +154260,9 @@ Nothing was written.`;
153156
154260
  sessionId,
153157
154261
  directory: sessionDirectory,
153158
154262
  historyBudgetTokens: deps.historyBudgetTokens,
153159
- historianTimeoutMs
154263
+ historianTimeoutMs,
154264
+ minCompartmentRatio: deps.compressorMinCompartmentRatio,
154265
+ maxMergeDepth: deps.compressorMaxMergeDepth
153160
154266
  });
153161
154267
  }
153162
154268
  return [
@@ -153182,6 +154288,7 @@ Staging data preserved for resume on next attempt.`;
153182
154288
  }
153183
154289
  var init_compartment_runner_recomp = __esm(() => {
153184
154290
  init_compartment_storage();
154291
+ init_compression_depth_storage();
153185
154292
  init_memory();
153186
154293
  init_project_identity();
153187
154294
  init_storage_memory();
@@ -153204,12 +154311,21 @@ var exports_compartment_runner = {};
153204
154311
  __export(exports_compartment_runner, {
153205
154312
  startCompartmentAgent: () => startCompartmentAgent,
153206
154313
  runCompartmentAgent: () => runCompartmentAgent,
154314
+ registerActiveCompartmentRun: () => registerActiveCompartmentRun,
153207
154315
  getActiveCompartmentRun: () => getActiveCompartmentRun,
153208
154316
  executeContextRecomp: () => executeContextRecomp
153209
154317
  });
153210
154318
  function getActiveCompartmentRun(sessionId) {
153211
154319
  return activeRuns.get(sessionId);
153212
154320
  }
154321
+ function registerActiveCompartmentRun(sessionId, promise2) {
154322
+ const wrapped = promise2.finally(() => {
154323
+ if (activeRuns.get(sessionId) === wrapped) {
154324
+ activeRuns.delete(sessionId);
154325
+ }
154326
+ });
154327
+ activeRuns.set(sessionId, wrapped);
154328
+ }
153213
154329
  function startCompartmentAgent(deps) {
153214
154330
  const existing = activeRuns.get(deps.sessionId);
153215
154331
  if (existing) {
@@ -153225,12 +154341,12 @@ function startCompartmentAgent(deps) {
153225
154341
  });
153226
154342
  activeRuns.set(deps.sessionId, promise2);
153227
154343
  }
153228
- async function executeContextRecomp(deps) {
154344
+ async function executeContextRecomp(deps, options = {}) {
153229
154345
  const { sessionId } = deps;
153230
154346
  if (activeRuns.has(sessionId)) {
153231
154347
  return "## Magic Recomp\n\nHistorian is already running for this session. Wait for it to finish, then try `/ctx-recomp` again.";
153232
154348
  }
153233
- const promise2 = executeContextRecompInternal(deps);
154349
+ const promise2 = options.range ? executePartialRecompInternal(deps, options.range) : executeContextRecompInternal(deps);
153234
154350
  activeRuns.set(sessionId, promise2.then(() => {
153235
154351
  return;
153236
154352
  }).catch((err) => {
@@ -153247,6 +154363,7 @@ var init_compartment_runner = __esm(() => {
153247
154363
  init_storage_meta();
153248
154364
  init_logger();
153249
154365
  init_compartment_runner_incremental();
154366
+ init_compartment_runner_partial_recomp();
153250
154367
  init_compartment_runner_recomp();
153251
154368
  init_compartment_runner_incremental();
153252
154369
  activeRuns = new Map;
@@ -161014,15 +162131,15 @@ var exports_tui_config = {};
161014
162131
  __export(exports_tui_config, {
161015
162132
  ensureTuiPluginEntry: () => ensureTuiPluginEntry
161016
162133
  });
161017
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
161018
- import { dirname as dirname2, join as join16 } from "path";
162134
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
162135
+ import { dirname as dirname3, join as join16 } from "path";
161019
162136
  function resolveTuiConfigPath() {
161020
162137
  const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
161021
162138
  const jsoncPath = join16(configDir, "tui.jsonc");
161022
162139
  const jsonPath = join16(configDir, "tui.json");
161023
- if (existsSync7(jsoncPath))
162140
+ if (existsSync8(jsoncPath))
161024
162141
  return jsoncPath;
161025
- if (existsSync7(jsonPath))
162142
+ if (existsSync8(jsonPath))
161026
162143
  return jsonPath;
161027
162144
  return jsonPath;
161028
162145
  }
@@ -161030,8 +162147,8 @@ function ensureTuiPluginEntry() {
161030
162147
  try {
161031
162148
  const configPath = resolveTuiConfigPath();
161032
162149
  let config2 = {};
161033
- if (existsSync7(configPath)) {
161034
- const raw = readFileSync6(configPath, "utf-8");
162150
+ if (existsSync8(configPath)) {
162151
+ const raw = readFileSync7(configPath, "utf-8");
161035
162152
  config2 = import_comment_json.parse(raw) ?? {};
161036
162153
  }
161037
162154
  const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((p) => typeof p === "string") : [];
@@ -161050,7 +162167,7 @@ function ensureTuiPluginEntry() {
161050
162167
  plugins.push(PLUGIN_ENTRY);
161051
162168
  }
161052
162169
  config2.plugin = plugins;
161053
- mkdirSync5(dirname2(configPath), { recursive: true });
162170
+ mkdirSync5(dirname3(configPath), { recursive: true });
161054
162171
  writeFileSync3(configPath, `${import_comment_json.stringify(config2, null, 2)}
161055
162172
  `);
161056
162173
  log(`[magic-context] updated TUI plugin entry in ${configPath}`);
@@ -161070,12 +162187,76 @@ var init_tui_config = __esm(() => {
161070
162187
  // src/config/index.ts
161071
162188
  init_jsonc_parser();
161072
162189
  init_magic_context();
162190
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
162191
+ import { homedir as homedir2 } from "os";
162192
+ import { join } from "path";
162193
+
162194
+ // src/config/variable.ts
161073
162195
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
161074
162196
  import { homedir } from "os";
161075
- import { join } from "path";
162197
+ import { dirname, isAbsolute, resolve } from "path";
162198
+ var ENV_PATTERN = /\{env:([^}]+)\}/g;
162199
+ var FILE_PATTERN = /\{file:([^}]+)\}/g;
162200
+ function substituteConfigVariables(input) {
162201
+ const warnings = [];
162202
+ let text = input.text;
162203
+ text = text.replace(ENV_PATTERN, (_, rawName) => {
162204
+ const varName = rawName.trim();
162205
+ const value = varName ? process.env[varName] : undefined;
162206
+ if (value === undefined || value === "") {
162207
+ warnings.push(`Environment variable ${varName} is not set (referenced via {env:${varName}}); using empty string`);
162208
+ return "";
162209
+ }
162210
+ return value;
162211
+ });
162212
+ const fileMatches = Array.from(text.matchAll(FILE_PATTERN));
162213
+ if (fileMatches.length === 0) {
162214
+ return { text, warnings };
162215
+ }
162216
+ const configDir = input.configPath ? dirname(input.configPath) : process.cwd();
162217
+ let output = "";
162218
+ let cursor = 0;
162219
+ for (const match of fileMatches) {
162220
+ const token = match[0];
162221
+ const rawPath = match[1] ?? "";
162222
+ const index = match.index ?? 0;
162223
+ output += text.slice(cursor, index);
162224
+ cursor = index + token.length;
162225
+ const lineStart = text.lastIndexOf(`
162226
+ `, index - 1) + 1;
162227
+ const prefix = text.slice(lineStart, index).trimStart();
162228
+ if (prefix.startsWith("//")) {
162229
+ output += token;
162230
+ continue;
162231
+ }
162232
+ let filePath = rawPath.trim();
162233
+ if (filePath.startsWith("~/")) {
162234
+ filePath = resolve(homedir(), filePath.slice(2));
162235
+ } else if (!isAbsolute(filePath)) {
162236
+ filePath = resolve(configDir, filePath);
162237
+ }
162238
+ if (!existsSync2(filePath)) {
162239
+ warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
162240
+ continue;
162241
+ }
162242
+ let contents;
162243
+ try {
162244
+ contents = readFileSync2(filePath, "utf-8").trim();
162245
+ } catch (error48) {
162246
+ const message = error48 instanceof Error ? error48.message : String(error48);
162247
+ warnings.push(`Failed to read file for ${token} (${filePath}): ${message}; using empty string`);
162248
+ continue;
162249
+ }
162250
+ output += JSON.stringify(contents).slice(1, -1);
162251
+ }
162252
+ output += text.slice(cursor);
162253
+ return { text: output, warnings };
162254
+ }
162255
+
162256
+ // src/config/index.ts
161076
162257
  var CONFIG_FILE_BASENAME = "magic-context";
161077
162258
  function getUserConfigBasePath() {
161078
- const configRoot = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
162259
+ const configRoot = process.env.XDG_CONFIG_HOME ?? join(homedir2(), ".config");
161079
162260
  return join(configRoot, "opencode", CONFIG_FILE_BASENAME);
161080
162261
  }
161081
162262
  function getProjectConfigBasePath(directory) {
@@ -161083,10 +162264,15 @@ function getProjectConfigBasePath(directory) {
161083
162264
  }
161084
162265
  function loadConfigFile(configPath) {
161085
162266
  try {
161086
- if (!existsSync2(configPath)) {
162267
+ if (!existsSync3(configPath)) {
161087
162268
  return null;
161088
162269
  }
161089
- return parseJsonc(readFileSync2(configPath, "utf-8"));
162270
+ const rawText = readFileSync3(configPath, "utf-8");
162271
+ const substituted = substituteConfigVariables({ text: rawText, configPath });
162272
+ return {
162273
+ config: parseJsonc(substituted.text),
162274
+ warnings: substituted.warnings.map((w) => `${configPath}: ${w}`)
162275
+ };
161090
162276
  } catch (error48) {
161091
162277
  console.warn(`[magic-context] failed to load config from ${configPath}:`, error48 instanceof Error ? error48.message : String(error48));
161092
162278
  return null;
@@ -161120,6 +162306,25 @@ function mergeConfigs(base, override) {
161120
162306
  };
161121
162307
  return config2;
161122
162308
  }
162309
+ function redactConfigValue(value) {
162310
+ if (value === undefined)
162311
+ return "<missing>";
162312
+ if (value === null)
162313
+ return "null";
162314
+ if (typeof value === "string")
162315
+ return `string, ${value.length} char${value.length === 1 ? "" : "s"}`;
162316
+ if (typeof value === "number")
162317
+ return `number ${value}`;
162318
+ if (typeof value === "boolean")
162319
+ return `boolean ${value}`;
162320
+ if (Array.isArray(value))
162321
+ return `array, ${value.length} item${value.length === 1 ? "" : "s"}`;
162322
+ if (typeof value === "object") {
162323
+ const keys = Object.keys(value);
162324
+ return `object with keys [${keys.join(", ")}]`;
162325
+ }
162326
+ return typeof value;
162327
+ }
161123
162328
  function parsePluginConfig(rawConfig) {
161124
162329
  const parsed = MagicContextConfigSchema.safeParse(rawConfig);
161125
162330
  const disabledHooks = Array.isArray(rawConfig.disabled_hooks) ? rawConfig.disabled_hooks.filter((value) => typeof value === "string") : undefined;
@@ -161149,7 +162354,7 @@ function parsePluginConfig(rawConfig) {
161149
162354
  } else {
161150
162355
  delete patched[key];
161151
162356
  const defaultVal = defaults[key];
161152
- warnings.push(`"${key}": invalid value ${JSON.stringify(rawConfig[key])}, using default ${JSON.stringify(defaultVal)}.`);
162357
+ warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultVal)}.`);
161153
162358
  }
161154
162359
  }
161155
162360
  const retryParsed = MagicContextConfigSchema.safeParse(patched);
@@ -161169,19 +162374,21 @@ function loadPluginConfig(directory) {
161169
162374
  const rootDetected = detectConfigFile(join(directory, CONFIG_FILE_BASENAME));
161170
162375
  const dotOpenCodeDetected = detectConfigFile(getProjectConfigBasePath(directory));
161171
162376
  const projectDetected = rootDetected.format !== "none" ? rootDetected : dotOpenCodeDetected;
161172
- const userConfig = userDetected.format === "none" ? null : loadConfigFile(userDetected.path);
161173
- const projectConfig = projectDetected.format === "none" ? null : loadConfigFile(projectDetected.path);
162377
+ const userLoaded = userDetected.format === "none" ? null : loadConfigFile(userDetected.path);
162378
+ const projectLoaded = projectDetected.format === "none" ? null : loadConfigFile(projectDetected.path);
161174
162379
  let config2 = parsePluginConfig({});
161175
162380
  const allWarnings = [];
161176
- if (userConfig) {
161177
- const parsed = parsePluginConfig(userConfig);
162381
+ if (userLoaded) {
162382
+ allWarnings.push(...userLoaded.warnings.map((w) => `[user config] ${w}`));
162383
+ const parsed = parsePluginConfig(userLoaded.config);
161178
162384
  if (parsed.configWarnings?.length) {
161179
162385
  allWarnings.push(...parsed.configWarnings.map((w) => `[user config] ${w}`));
161180
162386
  }
161181
162387
  config2 = mergeConfigs(config2, parsed);
161182
162388
  }
161183
- if (projectConfig) {
161184
- const parsed = parsePluginConfig(projectConfig);
162389
+ if (projectLoaded) {
162390
+ allWarnings.push(...projectLoaded.warnings.map((w) => `[project config] ${w}`));
162391
+ const parsed = parsePluginConfig(projectLoaded.config);
161185
162392
  if (parsed.configWarnings?.length) {
161186
162393
  allWarnings.push(...parsed.configWarnings.map((w) => `[project config] ${w}`));
161187
162394
  }
@@ -161834,7 +163041,7 @@ init_assistant_message_extractor();
161834
163041
  init_data_path();
161835
163042
  init_logger();
161836
163043
  import { Database as Database2 } from "bun:sqlite";
161837
- import { existsSync as existsSync4 } from "fs";
163044
+ import { existsSync as existsSync5 } from "fs";
161838
163045
  import { join as join8 } from "path";
161839
163046
 
161840
163047
  // src/features/magic-context/key-files/identify-key-files.ts
@@ -162230,7 +163437,7 @@ function getOpenCodeDbPath2() {
162230
163437
  }
162231
163438
  function openOpenCodeDb() {
162232
163439
  const dbPath = getOpenCodeDbPath2();
162233
- if (!existsSync4(dbPath)) {
163440
+ if (!existsSync5(dbPath)) {
162234
163441
  log(`[key-files] OpenCode DB not found at ${dbPath} \u2014 skipping`);
162235
163442
  return null;
162236
163443
  }
@@ -162480,8 +163687,8 @@ async function runDream(args) {
162480
163687
  try {
162481
163688
  const docsDir = args.sessionDirectory ?? args.projectIdentity;
162482
163689
  const existingDocs = taskName === "maintain-docs" ? {
162483
- architecture: existsSync4(join8(docsDir, "ARCHITECTURE.md")),
162484
- structure: existsSync4(join8(docsDir, "STRUCTURE.md"))
163690
+ architecture: existsSync5(join8(docsDir, "ARCHITECTURE.md")),
163691
+ structure: existsSync5(join8(docsDir, "STRUCTURE.md"))
162485
163692
  } : undefined;
162486
163693
  const userMemories = taskName === "archive-stale" ? getActiveUserMemories(args.db).map((um) => ({
162487
163694
  id: um.id,
@@ -163372,6 +164579,7 @@ init_logger();
163372
164579
  init_storage();
163373
164580
  init_shared();
163374
164581
  init_rpc_notifications();
164582
+ init_compartment_runner_partial_recomp();
163375
164583
 
163376
164584
  // src/hooks/magic-context/execute-flush.ts
163377
164585
  init_storage();
@@ -163727,6 +164935,37 @@ ${c.content}
163727
164935
  init_send_session_notification();
163728
164936
  var recompConfirmationBySession = new Map;
163729
164937
  var RECOMP_CONFIRMATION_WINDOW_MS = 60000;
164938
+ function parseRecompArgs(raw) {
164939
+ const trimmed = raw.trim();
164940
+ if (trimmed === "")
164941
+ return { kind: "full" };
164942
+ const match = trimmed.match(/^(\d+)\s*-\s*(\d+)$/);
164943
+ if (!match) {
164944
+ return {
164945
+ kind: "error",
164946
+ message: `Invalid /ctx-recomp arguments: \`${trimmed}\`.
164947
+
164948
+ Usage:
164949
+ - \`/ctx-recomp\` \u2014 full rebuild from message 1 to the protected tail
164950
+ - \`/ctx-recomp <start>-<end>\` \u2014 partial rebuild of a message range (e.g. \`/ctx-recomp 1-11322\`)`
164951
+ };
164952
+ }
164953
+ const start = Number.parseInt(match[1], 10);
164954
+ const end = Number.parseInt(match[2], 10);
164955
+ if (!Number.isFinite(start) || !Number.isFinite(end)) {
164956
+ return { kind: "error", message: "Range values must be finite integers." };
164957
+ }
164958
+ if (start < 1) {
164959
+ return { kind: "error", message: `Start must be >= 1 (got ${start}).` };
164960
+ }
164961
+ if (end < start) {
164962
+ return {
164963
+ kind: "error",
164964
+ message: `End must be >= start (got ${start}-${end}).`
164965
+ };
164966
+ }
164967
+ return { kind: "partial", range: { start, end } };
164968
+ }
163730
164969
  var SENTINEL_PREFIX = "__CONTEXT_MANAGEMENT_";
163731
164970
  function throwSentinel(command) {
163732
164971
  throw new Error(`${SENTINEL_PREFIX}${command.toUpperCase()}_HANDLED__`);
@@ -163856,43 +165095,95 @@ function createMagicContextCommandHandler(deps) {
163856
165095
  ${statusOutput}` : statusOutput;
163857
165096
  }
163858
165097
  if (isRecomp) {
163859
- if (isTuiConnected()) {
165098
+ const parsedArgs = parseRecompArgs(input.arguments);
165099
+ if (parsedArgs.kind === "error") {
165100
+ result = `## Magic Recomp \u2014 Invalid Arguments
165101
+
165102
+ ${parsedArgs.message}`;
165103
+ } else if (isTuiConnected()) {
163860
165104
  pushNotification("action", { action: "show-recomp-dialog" }, sessionId);
163861
165105
  sessionLog(sessionId, "command ctx-recomp: pushed show-recomp-dialog to TUI");
163862
165106
  throwSentinel(input.command);
163863
- }
163864
- if (!deps.executeRecomp) {
165107
+ } else if (!deps.executeRecomp) {
163865
165108
  result = `## Magic Recomp
163866
165109
 
163867
165110
  /ctx-recomp is unavailable because the recomp handler is not configured.`;
163868
165111
  } else {
165112
+ const argsKey = parsedArgs.kind === "partial" ? `${parsedArgs.range.start}-${parsedArgs.range.end}` : "";
163869
165113
  const lastConfirmation = recompConfirmationBySession.get(sessionId);
163870
165114
  const now = Date.now();
163871
- if (lastConfirmation && now - lastConfirmation < RECOMP_CONFIRMATION_WINDOW_MS) {
165115
+ const confirmationValid = lastConfirmation && now - lastConfirmation.timestamp < RECOMP_CONFIRMATION_WINDOW_MS && lastConfirmation.argsKey === argsKey;
165116
+ if (confirmationValid) {
163872
165117
  recompConfirmationBySession.delete(sessionId);
163873
- await deps.sendNotification(sessionId, `## Magic Recomp
165118
+ if (parsedArgs.kind === "partial") {
165119
+ await deps.sendNotification(sessionId, `## Magic Recomp
165120
+
165121
+ Partial recomp started for range ${parsedArgs.range.start}-${parsedArgs.range.end}. Rebuilding the matching compartments now (facts unchanged).`, {});
165122
+ result = await deps.executeRecomp(sessionId, {
165123
+ range: parsedArgs.range
165124
+ });
165125
+ } else {
165126
+ await deps.sendNotification(sessionId, `## Magic Recomp
163874
165127
 
163875
165128
  Historian recomp started. Rebuilding compartments and facts from raw session history now.`, {});
163876
- result = await deps.executeRecomp(sessionId);
165129
+ result = await deps.executeRecomp(sessionId);
165130
+ }
163877
165131
  } else {
163878
- recompConfirmationBySession.set(sessionId, now);
165132
+ recompConfirmationBySession.set(sessionId, {
165133
+ timestamp: now,
165134
+ argsKey
165135
+ });
163879
165136
  const compartments = getCompartments(deps.db, sessionId);
163880
165137
  const compartmentCount = compartments.length;
163881
- const warningLines = [
163882
- "## \u26A0\uFE0F Recomp Confirmation Required",
163883
- "",
163884
- `You currently have **${compartmentCount}** compartments.`,
163885
- "Running /ctx-recomp will **regenerate all compartments and facts** from raw session history.",
163886
- "",
163887
- "This operation:",
163888
- "- May take a long time (minutes to hours for long sessions)",
163889
- "- Will consume significant tokens on your historian model",
163890
- "- Cannot be interrupted cleanly once started",
163891
- "",
163892
- "**To confirm, run `/ctx-recomp` again within 60 seconds.**"
163893
- ];
163894
- result = warningLines.join(`
165138
+ if (parsedArgs.kind === "partial") {
165139
+ const snap = snapRangeToCompartments(compartments, parsedArgs.range);
165140
+ if ("error" in snap) {
165141
+ recompConfirmationBySession.delete(sessionId);
165142
+ result = `## Magic Recomp \u2014 Failed
165143
+
165144
+ ${snap.error}`;
165145
+ } else {
165146
+ const replaced = snap.rangeCompartments.length;
165147
+ const preserved = snap.priorCompartments.length + snap.tailCompartments.length;
165148
+ const warningLines = [
165149
+ "## \u26A0\uFE0F Partial Recomp Confirmation Required",
165150
+ "",
165151
+ `Requested range: \`${parsedArgs.range.start}-${parsedArgs.range.end}\``,
165152
+ `Snapped to compartment boundaries: **messages ${snap.snapStart}-${snap.snapEnd}**`,
165153
+ "",
165154
+ `This will **rebuild ${replaced} compartment(s)** in the snapped range.`,
165155
+ `**${preserved} compartment(s)** outside the range will be preserved unchanged.`,
165156
+ "Facts will not be re-extracted.",
165157
+ "",
165158
+ "This operation:",
165159
+ "- May take several minutes to tens of minutes depending on range size",
165160
+ "- Will consume historian-model tokens for each chunk",
165161
+ "- Is resumable if interrupted (staging preserved on failure)",
165162
+ "",
165163
+ `**To confirm, run \`/ctx-recomp ${parsedArgs.range.start}-${parsedArgs.range.end}\` again within 60 seconds.**`
165164
+ ];
165165
+ result = warningLines.join(`
163895
165166
  `);
165167
+ }
165168
+ } else {
165169
+ const warningLines = [
165170
+ "## \u26A0\uFE0F Recomp Confirmation Required",
165171
+ "",
165172
+ `You currently have **${compartmentCount}** compartments.`,
165173
+ "Running /ctx-recomp will **regenerate all compartments and facts** from raw session history.",
165174
+ "",
165175
+ "This operation:",
165176
+ "- May take a long time (minutes to hours for long sessions)",
165177
+ "- Will consume significant tokens on your historian model",
165178
+ "- Cannot be interrupted cleanly once started",
165179
+ "",
165180
+ "Tip: to rebuild only a specific message range, use `/ctx-recomp <start>-<end>` (e.g. `/ctx-recomp 1-11322`).",
165181
+ "",
165182
+ "**To confirm, run `/ctx-recomp` again within 60 seconds.**"
165183
+ ];
165184
+ result = warningLines.join(`
165185
+ `);
165186
+ }
163896
165187
  }
163897
165188
  }
163898
165189
  }
@@ -164515,7 +165806,11 @@ function stripProcessedImages(messages, watermark, messageTagNumbers) {
164515
165806
  return stripped;
164516
165807
  }
164517
165808
 
165809
+ // src/hooks/magic-context/transform.ts
165810
+ init_temporal_awareness();
165811
+
164518
165812
  // src/hooks/magic-context/transform-compartment-phase.ts
165813
+ init_magic_context();
164519
165814
  init_compartment_storage();
164520
165815
  init_storage();
164521
165816
  init_logger();
@@ -164525,11 +165820,11 @@ init_inject_compartments();
164525
165820
  init_read_session_chunk();
164526
165821
  init_send_session_notification();
164527
165822
  var lastCompressorRunBySession = new Map;
164528
- function isCompressorOnCooldown(sessionId) {
165823
+ function isCompressorOnCooldown(sessionId, cooldownMs) {
164529
165824
  const lastRun = lastCompressorRunBySession.get(sessionId);
164530
165825
  if (!lastRun)
164531
165826
  return false;
164532
- return Date.now() - lastRun < 600000;
165827
+ return Date.now() - lastRun < cooldownMs;
164533
165828
  }
164534
165829
  function markCompressorRun(sessionId) {
164535
165830
  lastCompressorRunBySession.set(sessionId, Date.now());
@@ -164566,14 +165861,14 @@ async function runCompartmentPhase(args) {
164566
165861
  async function awaitCompartmentRun(activeRun, reason) {
164567
165862
  sessionLog(args.sessionId, reason);
164568
165863
  const timeoutMs = args.historianTimeoutMs ?? 120000;
164569
- const timeout = new Promise((resolve2) => setTimeout(() => resolve2("timeout"), timeoutMs));
165864
+ const timeout = new Promise((resolve3) => setTimeout(() => resolve3("timeout"), timeoutMs));
164570
165865
  const result = await Promise.race([activeRun.then(() => "done"), timeout]);
164571
165866
  if (result === "timeout") {
164572
165867
  sessionLog(args.sessionId, `transform: compartment await timed out after ${timeoutMs}ms \u2014 proceeding without waiting`);
164573
165868
  return "timed_out";
164574
165869
  }
164575
165870
  sessionLog(args.sessionId, "transform: compartment agent completed, refreshing compartment coverage");
164576
- pendingCompartmentInjection = prepareCompartmentInjection(args.db, args.resolvedSessionId, args.messages, args.cacheAlreadyBusting ?? false, args.projectPath, args.injectionBudgetTokens);
165871
+ pendingCompartmentInjection = prepareCompartmentInjection(args.db, args.resolvedSessionId, args.messages, args.cacheAlreadyBusting ?? false, args.projectPath, args.injectionBudgetTokens, args.experimentalTemporalAwareness);
164577
165872
  return "completed";
164578
165873
  }
164579
165874
  if (args.canRunCompartments && args.sessionMeta.compartmentInProgress && !getActiveCompartmentRun(args.sessionId)) {
@@ -164598,7 +165893,10 @@ async function runCompartmentPhase(args) {
164598
165893
  fallbackModelId: args.fallbackModelId,
164599
165894
  getNotificationParams: args.getNotificationParams,
164600
165895
  experimentalCompactionMarkers: args.experimentalCompactionMarkers,
164601
- experimentalUserMemories: args.experimentalUserMemories
165896
+ experimentalUserMemories: args.experimentalUserMemories,
165897
+ historianTwoPass: args.historianTwoPass,
165898
+ compressorMinCompartmentRatio: args.compressorMinCompartmentRatio,
165899
+ compressorMaxMergeDepth: args.compressorMaxMergeDepth
164602
165900
  });
164603
165901
  compartmentInProgress = true;
164604
165902
  }
@@ -164619,7 +165917,10 @@ async function runCompartmentPhase(args) {
164619
165917
  fallbackModelId: args.fallbackModelId,
164620
165918
  getNotificationParams: args.getNotificationParams,
164621
165919
  experimentalCompactionMarkers: args.experimentalCompactionMarkers,
164622
- experimentalUserMemories: args.experimentalUserMemories
165920
+ experimentalUserMemories: args.experimentalUserMemories,
165921
+ historianTwoPass: args.historianTwoPass,
165922
+ compressorMinCompartmentRatio: args.compressorMinCompartmentRatio,
165923
+ compressorMaxMergeDepth: args.compressorMaxMergeDepth
164623
165924
  });
164624
165925
  activeRun = getActiveCompartmentRun(args.sessionId);
164625
165926
  } else if (!activeRun && hasEligibleHistoryForCompartment()) {
@@ -164639,15 +165940,17 @@ async function runCompartmentPhase(args) {
164639
165940
  }
164640
165941
  }
164641
165942
  }
164642
- if (args.cacheAlreadyBusting && args.historyBudgetTokens && args.historyBudgetTokens > 0 && args.client && !compartmentInProgress && !awaitedCompartmentRun && !isCompressorOnCooldown(args.sessionId)) {
165943
+ if (args.cacheAlreadyBusting && args.historyBudgetTokens && args.historyBudgetTokens > 0 && args.client && !compartmentInProgress && !awaitedCompartmentRun && !isCompressorOnCooldown(args.sessionId, args.compressorCooldownMs ?? DEFAULT_COMPRESSOR_COOLDOWN_MS)) {
164643
165944
  markCompressorRun(args.sessionId);
164644
- runCompressionPassIfNeeded({
165945
+ const compressorPromise = runCompressionPassIfNeeded({
164645
165946
  client: args.client,
164646
165947
  db: args.db,
164647
165948
  sessionId: args.sessionId,
164648
165949
  directory: args.compartmentDirectory,
164649
165950
  historyBudgetTokens: args.historyBudgetTokens,
164650
- historianTimeoutMs: args.historianTimeoutMs
165951
+ historianTimeoutMs: args.historianTimeoutMs,
165952
+ minCompartmentRatio: args.compressorMinCompartmentRatio,
165953
+ maxMergeDepth: args.compressorMaxMergeDepth
164651
165954
  }).then((compressed) => {
164652
165955
  if (compressed) {
164653
165956
  sessionLog(args.sessionId, "independent compressor completed in background \u2014 compressed history will appear on next cache-busting pass");
@@ -164655,6 +165958,7 @@ async function runCompartmentPhase(args) {
164655
165958
  }).catch((error48) => {
164656
165959
  sessionLog(args.sessionId, "independent compressor failed in background:", getErrorMessage(error48));
164657
165960
  });
165961
+ registerActiveCompartmentRun(args.sessionId, compressorPromise);
164658
165962
  }
164659
165963
  return { pendingCompartmentInjection, awaitedCompartmentRun, compartmentInProgress };
164660
165964
  }
@@ -166205,7 +167509,11 @@ function createTransform(deps) {
166205
167509
  fallbackModelId,
166206
167510
  getNotificationParams: () => notificationParams,
166207
167511
  experimentalCompactionMarkers: deps.experimentalCompactionMarkers,
166208
- experimentalUserMemories: deps.experimentalUserMemories
167512
+ experimentalUserMemories: deps.experimentalUserMemories,
167513
+ experimentalTemporalAwareness: deps.experimentalTemporalAwareness,
167514
+ historianTwoPass: deps.historianTwoPass,
167515
+ compressorMinCompartmentRatio: deps.compressorMinCompartmentRatio,
167516
+ compressorMaxMergeDepth: deps.compressorMaxMergeDepth
166209
167517
  });
166210
167518
  skipCompartmentAwaitForThisPass = true;
166211
167519
  return true;
@@ -166239,7 +167547,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
166239
167547
  if (fullFeatureMode) {
166240
167548
  const tInj = performance.now();
166241
167549
  const projectPath = deps.memoryConfig?.enabled ? resolveProjectIdentity(deps.directory ?? process.cwd()) : undefined;
166242
- pendingCompartmentInjection = prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, projectPath, deps.memoryConfig?.injectionBudgetTokens);
167550
+ pendingCompartmentInjection = prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, projectPath, deps.memoryConfig?.injectionBudgetTokens, deps.experimentalTemporalAwareness);
166243
167551
  logTransformTiming(sessionId, "prepareCompartmentInjection", tInj);
166244
167552
  }
166245
167553
  let targets = new Map;
@@ -166247,6 +167555,14 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
166247
167555
  let messageTagNumbers = new Map;
166248
167556
  let batch = null;
166249
167557
  let hasRecentReduceCall = false;
167558
+ if (deps.experimentalTemporalAwareness) {
167559
+ const tTemporal = performance.now();
167560
+ const injected = injectTemporalMarkers(messages);
167561
+ if (injected > 0) {
167562
+ sessionLog(sessionId, `temporal: injected ${injected} gap markers`);
167563
+ }
167564
+ logTransformTiming(sessionId, "injectTemporalMarkers", tTemporal);
167565
+ }
166250
167566
  try {
166251
167567
  const t0 = performance.now();
166252
167568
  deps.tagger.initFromDb(sessionId, db);
@@ -166338,7 +167654,12 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
166338
167654
  cacheAlreadyBusting: isCacheBusting || schedulerDecisionEarly === "execute",
166339
167655
  skipAwaitForThisPass: skipCompartmentAwaitForThisPass,
166340
167656
  experimentalCompactionMarkers: deps.experimentalCompactionMarkers,
166341
- experimentalUserMemories: deps.experimentalUserMemories
167657
+ experimentalUserMemories: deps.experimentalUserMemories,
167658
+ experimentalTemporalAwareness: deps.experimentalTemporalAwareness,
167659
+ historianTwoPass: deps.historianTwoPass,
167660
+ compressorMinCompartmentRatio: deps.compressorMinCompartmentRatio,
167661
+ compressorMaxMergeDepth: deps.compressorMaxMergeDepth,
167662
+ compressorCooldownMs: deps.compressorCooldownMs
166342
167663
  });
166343
167664
  pendingCompartmentInjection = compartmentPhase.pendingCompartmentInjection;
166344
167665
  const awaitedCompartmentRun = compartmentPhase.awaitedCompartmentRun;
@@ -166935,6 +168256,9 @@ function generateEmergencyNudgeText(db, sessionId, contextUsage, config2) {
166935
168256
  `);
166936
168257
  }
166937
168258
 
168259
+ // src/hooks/magic-context/hook.ts
168260
+ init_read_session_db();
168261
+
166938
168262
  // src/hooks/magic-context/text-complete.ts
166939
168263
  var TAG_PREFIX_REGEX2 = /^(\u00a7\d+\u00a7\s*)+/;
166940
168264
  function createTextCompleteHandler() {
@@ -167113,8 +168437,8 @@ function createToolExecuteAfterHook(args) {
167113
168437
  init_send_session_notification();
167114
168438
 
167115
168439
  // src/hooks/magic-context/system-prompt-hash.ts
167116
- import { existsSync as existsSync6, readFileSync as readFileSync5, realpathSync } from "fs";
167117
- import { join as join14, resolve as resolve2, sep } from "path";
168440
+ import { existsSync as existsSync7, readFileSync as readFileSync6, realpathSync } from "fs";
168441
+ import { join as join14, resolve as resolve3, sep } from "path";
167118
168442
 
167119
168443
  // src/agents/magic-context-prompt.ts
167120
168444
  function getToolHistoryGuidance(dropToolStructure) {
@@ -167300,20 +168624,23 @@ function detectAgentFromSystemPrompt(systemPrompt) {
167300
168624
  }
167301
168625
  return null;
167302
168626
  }
167303
- function buildMagicContextSection(agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, dropToolStructure = true) {
168627
+ var TEMPORAL_AWARENESS_GUIDANCE = `
168628
+ **Temporal awareness**: User messages may be preceded by HTML comments like \`<!-- +12m -->\`, \`<!-- +2h 15m -->\`, or \`<!-- +3d 4h -->\` indicating time elapsed since the previous message's completion. Compartments in \`<session-history>\` carry \`start-date\` and \`end-date\` attributes (YYYY-MM-DD) showing real-time boundaries. Use these when reasoning about workflow pacing, log durations, build times, or how long ago something happened.`;
168629
+ function buildMagicContextSection(agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, dropToolStructure = true, temporalAwarenessEnabled = false) {
167304
168630
  const smartNoteGuidance = dreamerEnabled ? `
167305
168631
  When \`surface_condition\` is provided with \`write\`, the note becomes a project-scoped smart note.
167306
168632
  The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
167307
168633
  Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`` : "";
168634
+ const temporalGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : "";
167308
168635
  if (!ctxReduceEnabled) {
167309
168636
  return `## Magic Context
167310
168637
 
167311
- ${BASE_INTRO_NO_REDUCE(dropToolStructure)}${smartNoteGuidance}`;
168638
+ ${BASE_INTRO_NO_REDUCE(dropToolStructure)}${smartNoteGuidance}${temporalGuidance}`;
167312
168639
  }
167313
168640
  const section = agent ? AGENT_SECTIONS[agent] : GENERIC_SECTION;
167314
168641
  return `## Magic Context
167315
168642
 
167316
- ${BASE_INTRO(protectedTags, dropToolStructure)}${smartNoteGuidance}
168643
+ ${BASE_INTRO(protectedTags, dropToolStructure)}${smartNoteGuidance}${temporalGuidance}
167317
168644
  ${section}
167318
168645
 
167319
168646
  Prefer many small targeted operations over one large blanket operation. Compress early and often \u2014 don't wait for warnings.`;
@@ -167336,8 +168663,8 @@ function readProjectDocs(directory) {
167336
168663
  for (const filename of DOC_FILES) {
167337
168664
  const filePath = join14(directory, filename);
167338
168665
  try {
167339
- if (existsSync6(filePath)) {
167340
- const content = readFileSync5(filePath, "utf-8").trim();
168666
+ if (existsSync7(filePath)) {
168667
+ const content = readFileSync6(filePath, "utf-8").trim();
167341
168668
  if (content.length > 0) {
167342
168669
  sections.push(`<${filename}>
167343
168670
  ${content}
@@ -167368,7 +168695,7 @@ function createSystemPromptHashHandler(deps) {
167368
168695
  `);
167369
168696
  if (fullPrompt.length > 0 && !fullPrompt.includes(MAGIC_CONTEXT_MARKER)) {
167370
168697
  const detectedAgent = detectAgentFromSystemPrompt(fullPrompt);
167371
- const guidance = buildMagicContextSection(detectedAgent, deps.protectedTags, deps.ctxReduceEnabled, deps.dreamerEnabled, deps.dropToolStructure);
168698
+ const guidance = buildMagicContextSection(detectedAgent, deps.protectedTags, deps.ctxReduceEnabled, deps.dreamerEnabled, deps.dropToolStructure, deps.experimentalTemporalAwareness);
167372
168699
  output.system.push(guidance);
167373
168700
  sessionLog(sessionId, `injected ${detectedAgent ?? "generic"} guidance into system prompt`);
167374
168701
  }
@@ -167417,16 +168744,16 @@ ${items}
167417
168744
  const keyFileEntries = getKeyFiles(deps.db, sessionId);
167418
168745
  if (keyFileEntries.length > 0) {
167419
168746
  const sections = [];
167420
- const projectRoot = resolve2(deps.directory);
168747
+ const projectRoot = resolve3(deps.directory);
167421
168748
  let remainingBudgetTokens = deps.experimentalPinKeyFilesTokenBudget ?? 1e4;
167422
168749
  for (const entry of keyFileEntries) {
167423
168750
  try {
167424
- const absPath = resolve2(deps.directory, entry.filePath);
168751
+ const absPath = resolve3(deps.directory, entry.filePath);
167425
168752
  if (!absPath.startsWith(projectRoot + sep) && absPath !== projectRoot) {
167426
168753
  log(`[magic-context] key file path escapes project root, skipping: ${entry.filePath}`);
167427
168754
  continue;
167428
168755
  }
167429
- if (!existsSync6(absPath))
168756
+ if (!existsSync7(absPath))
167430
168757
  continue;
167431
168758
  let realPath;
167432
168759
  try {
@@ -167438,7 +168765,7 @@ ${items}
167438
168765
  log(`[magic-context] key file symlink escapes project root, skipping: ${entry.filePath} \u2192 ${realPath}`);
167439
168766
  continue;
167440
168767
  }
167441
- const content = readFileSync5(realPath, "utf-8").trim();
168768
+ const content = readFileSync6(realPath, "utf-8").trim();
167442
168769
  if (content.length === 0)
167443
168770
  continue;
167444
168771
  const fileTokens = estimateTokens(content);
@@ -167576,6 +168903,17 @@ function createMagicContextHook(deps) {
167576
168903
  const agentBySession = deps.liveSessionState?.agentBySession ?? new Map;
167577
168904
  const recentReduceBySession = new Map;
167578
168905
  const toolUsageSinceUserTurn = new Map;
168906
+ const resolveLiveModel = (sessionId) => {
168907
+ const cached2 = liveModelBySession.get(sessionId);
168908
+ if (cached2)
168909
+ return cached2;
168910
+ const recovered = findLastAssistantModelFromOpenCodeDb(sessionId);
168911
+ if (recovered) {
168912
+ liveModelBySession.set(sessionId, recovered);
168913
+ return recovered;
168914
+ }
168915
+ return;
168916
+ };
167579
168917
  const ctxReduceEnabled = deps.config.ctx_reduce_enabled !== false;
167580
168918
  const nudgerWithRecentReduce = ctxReduceEnabled ? createNudger({
167581
168919
  protected_tags: deps.config.protected_tags,
@@ -167621,6 +168959,11 @@ function createMagicContextHook(deps) {
167621
168959
  projectPath,
167622
168960
  experimentalCompactionMarkers: deps.config.compaction_markers,
167623
168961
  experimentalUserMemories: deps.config.experimental?.user_memories?.enabled,
168962
+ experimentalTemporalAwareness: deps.config.experimental?.temporal_awareness === true,
168963
+ historianTwoPass: deps.config.historian?.two_pass === true,
168964
+ compressorMinCompartmentRatio: deps.config.compressor?.enabled === false ? undefined : deps.config.compressor?.min_compartment_ratio,
168965
+ compressorMaxMergeDepth: deps.config.compressor?.enabled === false ? undefined : deps.config.compressor?.max_merge_depth,
168966
+ compressorCooldownMs: deps.config.compressor?.enabled === false ? undefined : deps.config.compressor?.cooldown_ms,
167624
168967
  liveModelBySession
167625
168968
  });
167626
168969
  const eventHandler = createEventHandler2({
@@ -167679,17 +169022,17 @@ function createMagicContextHook(deps) {
167679
169022
  historyBudgetPercentage: deps.config.history_budget_percentage,
167680
169023
  commitClusterTrigger: deps.config.commit_cluster_trigger,
167681
169024
  getLiveModelKey: (sessionId) => {
167682
- const model = liveModelBySession.get(sessionId);
169025
+ const model = resolveLiveModel(sessionId);
167683
169026
  return model ? `${model.providerID}/${model.modelID}` : undefined;
167684
169027
  },
167685
169028
  getContextLimit: (sessionId) => {
167686
- const model = liveModelBySession.get(sessionId);
169029
+ const model = resolveLiveModel(sessionId);
167687
169030
  if (!model)
167688
169031
  return;
167689
169032
  return resolveContextLimit(model.providerID, model.modelID);
167690
169033
  },
167691
169034
  onFlush: (sessionId) => flushedSessions.add(sessionId),
167692
- executeRecomp: async (sessionId) => executeContextRecomp({
169035
+ executeRecomp: async (sessionId, options) => executeContextRecomp({
167693
169036
  client: deps.client,
167694
169037
  db,
167695
169038
  sessionId,
@@ -167697,11 +169040,12 @@ function createMagicContextHook(deps) {
167697
169040
  historianTimeoutMs: deps.config.historian_timeout_ms ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
167698
169041
  directory: deps.directory,
167699
169042
  fallbackModelId: (() => {
167700
- const model = liveModelBySession.get(sessionId);
169043
+ const model = resolveLiveModel(sessionId);
167701
169044
  return model ? `${model.providerID}/${model.modelID}` : undefined;
167702
169045
  })(),
167703
- getNotificationParams: () => getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession)
167704
- }),
169046
+ getNotificationParams: () => getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession),
169047
+ historianTwoPass: deps.config.historian?.two_pass === true
169048
+ }, options),
167705
169049
  sendNotification: async (sessionId, text, params) => {
167706
169050
  await sendIgnoredMessage(deps.client, sessionId, text, {
167707
169051
  ...getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession),
@@ -167743,7 +169087,8 @@ function createMagicContextHook(deps) {
167743
169087
  lastHeuristicsTurnId,
167744
169088
  experimentalUserMemories: deps.config.experimental?.user_memories?.enabled,
167745
169089
  experimentalPinKeyFiles: deps.config.experimental?.pin_key_files?.enabled ?? false,
167746
- experimentalPinKeyFilesTokenBudget: deps.config.experimental?.pin_key_files?.token_budget
169090
+ experimentalPinKeyFilesTokenBudget: deps.config.experimental?.pin_key_files?.token_budget,
169091
+ experimentalTemporalAwareness: deps.config.experimental?.temporal_awareness === true
167747
169092
  });
167748
169093
  const eventHook = createEventHook({
167749
169094
  eventHandler,
@@ -167827,6 +169172,7 @@ function createSessionHooks(args) {
167827
169172
  memory: pluginConfig.memory,
167828
169173
  sidekick: pluginConfig.sidekick,
167829
169174
  dreamer: pluginConfig.dreamer,
169175
+ compressor: pluginConfig.compressor,
167830
169176
  experimental: pluginConfig.experimental
167831
169177
  }
167832
169178
  })
@@ -169436,7 +170782,7 @@ init_models_dev_cache();
169436
170782
  init_logger();
169437
170783
  import { mkdirSync as mkdirSync4, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
169438
170784
  import { createServer } from "http";
169439
- import { dirname } from "path";
170785
+ import { dirname as dirname2 } from "path";
169440
170786
 
169441
170787
  // src/shared/rpc-utils.ts
169442
170788
  import { createHash as createHash2 } from "crypto";
@@ -169462,7 +170808,7 @@ class MagicContextRpcServer {
169462
170808
  this.handlers.set(method, handler);
169463
170809
  }
169464
170810
  async start() {
169465
- return new Promise((resolve3, reject) => {
170811
+ return new Promise((resolve4, reject) => {
169466
170812
  const server = createServer((req, res) => this.dispatch(req, res));
169467
170813
  server.on("error", (err) => {
169468
170814
  log(`[rpc] server error: ${err.message}`);
@@ -169477,7 +170823,7 @@ class MagicContextRpcServer {
169477
170823
  this.port = addr.port;
169478
170824
  this.server = server;
169479
170825
  try {
169480
- const dir = dirname(this.portFilePath);
170826
+ const dir = dirname2(this.portFilePath);
169481
170827
  mkdirSync4(dir, { recursive: true });
169482
170828
  const tmpPath = `${this.portFilePath}.tmp`;
169483
170829
  writeFileSync2(tmpPath, String(this.port), "utf-8");
@@ -169486,7 +170832,7 @@ class MagicContextRpcServer {
169486
170832
  } catch (err) {
169487
170833
  log(`[rpc] failed to write port file: ${err}`);
169488
170834
  }
169489
- resolve3(this.port);
170835
+ resolve4(this.port);
169490
170836
  });
169491
170837
  server.unref();
169492
170838
  });
@@ -169707,10 +171053,15 @@ var plugin = async (ctx) => {
169707
171053
  } = pluginConfig.sidekick;
169708
171054
  return agentOverrides;
169709
171055
  })() : undefined;
171056
+ const historianAgentOverrides = pluginConfig.historian ? (() => {
171057
+ const { two_pass: _twoPass, ...agentOverrides } = pluginConfig.historian;
171058
+ return agentOverrides;
171059
+ })() : undefined;
169710
171060
  config2.agent = {
169711
171061
  ...config2.agent ?? {},
169712
171062
  [DREAMER_AGENT]: buildHiddenAgentConfig(DREAMER_AGENT, DREAMER_SYSTEM_PROMPT, dreamerAgentOverrides),
169713
- [HISTORIAN_AGENT]: buildHiddenAgentConfig(HISTORIAN_AGENT, pluginConfig.experimental?.user_memories?.enabled ? COMPARTMENT_AGENT_SYSTEM_PROMPT + USER_OBSERVATIONS_APPENDIX : COMPARTMENT_AGENT_SYSTEM_PROMPT, pluginConfig.historian),
171063
+ [HISTORIAN_AGENT]: buildHiddenAgentConfig(HISTORIAN_AGENT, pluginConfig.experimental?.user_memories?.enabled ? COMPARTMENT_AGENT_SYSTEM_PROMPT + USER_OBSERVATIONS_APPENDIX : COMPARTMENT_AGENT_SYSTEM_PROMPT, historianAgentOverrides),
171064
+ [HISTORIAN_EDITOR_AGENT]: buildHiddenAgentConfig(HISTORIAN_EDITOR_AGENT, HISTORIAN_EDITOR_SYSTEM_PROMPT, historianAgentOverrides),
169714
171065
  [SIDEKICK_AGENT]: buildHiddenAgentConfig(SIDEKICK_AGENT, SIDEKICK_SYSTEM_PROMPT, sidekickAgentOverrides)
169715
171066
  };
169716
171067
  }