@cortexkit/opencode-magic-context 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/agents/historian.d.ts +1 -0
- package/dist/agents/historian.d.ts.map +1 -1
- package/dist/agents/magic-context-prompt.d.ts +1 -1
- package/dist/agents/magic-context-prompt.d.ts.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/prompts.d.ts.map +1 -1
- package/dist/cli.js +362 -83
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +103 -2
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/config/variable.d.ts +46 -0
- package/dist/config/variable.d.ts.map +1 -0
- package/dist/features/magic-context/compartment-storage.d.ts +27 -1
- package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
- package/dist/features/magic-context/compression-depth-storage.d.ts +7 -0
- package/dist/features/magic-context/compression-depth-storage.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-probe.d.ts +69 -0
- package/dist/features/magic-context/memory/embedding-probe.d.ts.map +1 -0
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/caveman.d.ts +35 -0
- package/dist/hooks/magic-context/caveman.d.ts.map +1 -0
- package/dist/hooks/magic-context/command-handler.d.ts +23 -1
- package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-prompt.d.ts +8 -3
- package/dist/hooks/magic-context/compartment-prompt.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-compressor.d.ts +46 -0
- package/dist/hooks/magic-context/compartment-runner-compressor.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-historian.d.ts +10 -0
- package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts +29 -0
- package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -0
- package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-types.d.ts +9 -0
- package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-validation.d.ts +5 -0
- package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner.d.ts +31 -1
- package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +9 -4
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts +11 -0
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts +13 -0
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-formatting.d.ts +7 -0
- package/dist/hooks/magic-context/read-session-formatting.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-prompt-hash.d.ts +2 -0
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/temporal-awareness.d.ts +73 -0
- package/dist/hooks/magic-context/temporal-awareness.d.ts.map +1 -0
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts +10 -0
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +14 -0
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2415 -1064
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/shared/model-requirements.d.ts.map +1 -1
- package/dist/shared/models-dev-cache.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/shared/model-requirements.ts +3 -1
- 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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
14717
|
-
lines.push("
|
|
14718
|
-
} else if (
|
|
14719
|
-
lines.push("
|
|
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("
|
|
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("
|
|
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
|
|
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
|
-
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
-
|
|
14783
|
-
|
|
14784
|
-
|
|
14785
|
-
|
|
14786
|
-
|
|
14787
|
-
U:
|
|
14788
|
-
|
|
14789
|
-
|
|
14790
|
-
|
|
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.`,
|
|
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
|
|
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
|
|
15097
|
+
return resolve2(envConfigDir);
|
|
14882
15098
|
}
|
|
14883
15099
|
if (process.platform === "win32") {
|
|
14884
|
-
return join3(
|
|
15100
|
+
return join3(homedir3(), ".config", "opencode");
|
|
14885
15101
|
}
|
|
14886
|
-
return join3(process.env.XDG_CONFIG_HOME || join3(
|
|
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
|
|
15094
|
-
import { homedir as
|
|
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 =
|
|
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 || !
|
|
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 =
|
|
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((
|
|
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((
|
|
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
|
|
150957
|
-
import { homedir as
|
|
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(
|
|
151249
|
+
cacheBase = process.env.LOCALAPPDATA ?? join11(homedir6(), "AppData", "Local");
|
|
150973
151250
|
} else {
|
|
150974
|
-
cacheBase = join11(
|
|
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(
|
|
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 (
|
|
151261
|
+
if (existsSync6(jsonc))
|
|
150985
151262
|
return jsonc;
|
|
150986
151263
|
const json2 = join11(configDir, "opencode.json");
|
|
150987
|
-
if (
|
|
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 (
|
|
151282
|
+
if (existsSync6(modelsJsonPath)) {
|
|
151006
151283
|
fileFound = true;
|
|
151007
|
-
const raw =
|
|
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 &&
|
|
151032
|
-
let raw =
|
|
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
|
-
|
|
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
|
|
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
|
|
152139
|
-
const
|
|
152140
|
-
|
|
152141
|
-
|
|
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
|
|
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
|
|
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
|
|
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:${
|
|
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:
|
|
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((
|
|
152458
|
-
setTimeout(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
161018
|
-
import { dirname as
|
|
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 (
|
|
162140
|
+
if (existsSync8(jsoncPath))
|
|
161024
162141
|
return jsoncPath;
|
|
161025
|
-
if (
|
|
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 (
|
|
161034
|
-
const raw =
|
|
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(
|
|
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 {
|
|
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(
|
|
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 (!
|
|
162267
|
+
if (!existsSync3(configPath)) {
|
|
161087
162268
|
return null;
|
|
161088
162269
|
}
|
|
161089
|
-
|
|
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 ${
|
|
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
|
|
161173
|
-
const
|
|
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 (
|
|
161177
|
-
|
|
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 (
|
|
161184
|
-
|
|
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
|
|
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 (!
|
|
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:
|
|
162484
|
-
structure:
|
|
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
|
-
|
|
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
|
-
|
|
165115
|
+
const confirmationValid = lastConfirmation && now - lastConfirmation.timestamp < RECOMP_CONFIRMATION_WINDOW_MS && lastConfirmation.argsKey === argsKey;
|
|
165116
|
+
if (confirmationValid) {
|
|
163872
165117
|
recompConfirmationBySession.delete(sessionId);
|
|
163873
|
-
|
|
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
|
-
|
|
165129
|
+
result = await deps.executeRecomp(sessionId);
|
|
165130
|
+
}
|
|
163877
165131
|
} else {
|
|
163878
|
-
recompConfirmationBySession.set(sessionId,
|
|
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
|
-
|
|
163882
|
-
|
|
163883
|
-
""
|
|
163884
|
-
|
|
163885
|
-
|
|
163886
|
-
|
|
163887
|
-
|
|
163888
|
-
|
|
163889
|
-
|
|
163890
|
-
|
|
163891
|
-
|
|
163892
|
-
|
|
163893
|
-
|
|
163894
|
-
|
|
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 <
|
|
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((
|
|
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
|
|
167117
|
-
import { join as join14, resolve as
|
|
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
|
-
|
|
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 (
|
|
167340
|
-
const content =
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
169025
|
+
const model = resolveLiveModel(sessionId);
|
|
167683
169026
|
return model ? `${model.providerID}/${model.modelID}` : undefined;
|
|
167684
169027
|
},
|
|
167685
169028
|
getContextLimit: (sessionId) => {
|
|
167686
|
-
const model =
|
|
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 =
|
|
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((
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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
|
}
|