@cortexkit/opencode-magic-context 0.13.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -7
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli.js +59 -3
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +61 -22
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/git-log-reader.d.ts +57 -0
- package/dist/features/magic-context/git-commits/git-log-reader.d.ts.map +1 -0
- package/dist/features/magic-context/git-commits/index.d.ts +7 -0
- package/dist/features/magic-context/git-commits/index.d.ts.map +1 -0
- package/dist/features/magic-context/git-commits/indexer.d.ts +44 -0
- package/dist/features/magic-context/git-commits/indexer.d.ts.map +1 -0
- package/dist/features/magic-context/git-commits/search-git-commits.d.ts +32 -0
- package/dist/features/magic-context/git-commits/search-git-commits.d.ts.map +1 -0
- package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts +18 -0
- package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts.map +1 -0
- package/dist/features/magic-context/git-commits/storage-git-commits.d.ts +47 -0
- package/dist/features/magic-context/git-commits/storage-git-commits.d.ts.map +1 -0
- package/dist/features/magic-context/memory/embedding-local.d.ts +2 -2
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-openai.d.ts +25 -2
- package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-provider.d.ts +8 -2
- package/dist/features/magic-context/memory/embedding-provider.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding.d.ts +23 -4
- package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/overflow-detection.d.ts +51 -0
- package/dist/features/magic-context/overflow-detection.d.ts.map +1 -0
- package/dist/features/magic-context/search.d.ts +40 -9
- package/dist/features/magic-context/search.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +40 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta.d.ts +1 -1
- package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -1
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-hint.d.ts +34 -0
- package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-runner.d.ts +51 -0
- package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -0
- package/dist/hooks/magic-context/compartment-runner-compressor.d.ts +36 -16
- package/dist/hooks/magic-context/compartment-runner-compressor.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-payloads.d.ts +15 -1
- package/dist/hooks/magic-context/event-payloads.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-resolvers.d.ts +20 -1
- package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook-handlers.d.ts +5 -0
- package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +14 -6
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts +12 -0
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
- package/dist/hooks/magic-context/reasoning-capability.d.ts +23 -0
- package/dist/hooks/magic-context/reasoning-capability.d.ts.map +1 -0
- package/dist/hooks/magic-context/strip-content.d.ts +3 -3
- package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +22 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +12 -0
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2161 -618
- package/dist/plugin/dream-timer.d.ts +24 -7
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +4 -0
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/tool-registry.d.ts.map +1 -1
- package/dist/shared/models-dev-cache.d.ts +20 -6
- package/dist/shared/models-dev-cache.d.ts.map +1 -1
- package/dist/tools/ctx-note/tools.d.ts.map +1 -1
- package/dist/tools/ctx-search/constants.d.ts +1 -1
- package/dist/tools/ctx-search/constants.d.ts.map +1 -1
- package/dist/tools/ctx-search/tools.d.ts.map +1 -1
- package/dist/tools/ctx-search/types.d.ts +8 -0
- package/dist/tools/ctx-search/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/shared/models-dev-cache.test.ts +57 -6
- package/src/shared/models-dev-cache.ts +119 -38
package/dist/index.js
CHANGED
|
@@ -14101,7 +14101,16 @@ var init_magic_context = __esm(() => {
|
|
|
14101
14101
|
max_runtime_minutes: exports_external.number().min(10).default(120),
|
|
14102
14102
|
tasks: exports_external.array(DreamingTaskSchema).default(DEFAULT_DREAMER_TASKS),
|
|
14103
14103
|
task_timeout_minutes: exports_external.number().min(5).default(20),
|
|
14104
|
-
inject_docs: exports_external.boolean().default(true)
|
|
14104
|
+
inject_docs: exports_external.boolean().default(true),
|
|
14105
|
+
user_memories: exports_external.object({
|
|
14106
|
+
enabled: exports_external.boolean().default(true),
|
|
14107
|
+
promotion_threshold: exports_external.number().min(2).max(20).default(3)
|
|
14108
|
+
}).default({ enabled: true, promotion_threshold: 3 }),
|
|
14109
|
+
pin_key_files: exports_external.object({
|
|
14110
|
+
enabled: exports_external.boolean().default(false),
|
|
14111
|
+
token_budget: exports_external.number().min(2000).max(30000).default(1e4),
|
|
14112
|
+
min_reads: exports_external.number().min(2).max(20).default(4)
|
|
14113
|
+
}).default({ enabled: false, token_budget: 1e4, min_reads: 4 })
|
|
14105
14114
|
}));
|
|
14106
14115
|
SidekickConfigSchema = AgentOverrideConfigSchema.extend({
|
|
14107
14116
|
enabled: exports_external.boolean().default(false),
|
|
@@ -14196,20 +14205,21 @@ var init_magic_context = __esm(() => {
|
|
|
14196
14205
|
model: DEFAULT_LOCAL_EMBEDDING_MODEL
|
|
14197
14206
|
}),
|
|
14198
14207
|
experimental: exports_external.object({
|
|
14199
|
-
|
|
14208
|
+
temporal_awareness: exports_external.boolean().default(false),
|
|
14209
|
+
git_commit_indexing: exports_external.object({
|
|
14200
14210
|
enabled: exports_external.boolean().default(false),
|
|
14201
|
-
|
|
14202
|
-
|
|
14203
|
-
|
|
14211
|
+
since_days: exports_external.number().min(7).max(3650).default(365),
|
|
14212
|
+
max_commits: exports_external.number().min(100).max(20000).default(2000)
|
|
14213
|
+
}).default({ enabled: false, since_days: 365, max_commits: 2000 }),
|
|
14214
|
+
auto_search: exports_external.object({
|
|
14204
14215
|
enabled: exports_external.boolean().default(false),
|
|
14205
|
-
|
|
14206
|
-
|
|
14207
|
-
}).default({ enabled: false,
|
|
14208
|
-
temporal_awareness: exports_external.boolean().default(false)
|
|
14216
|
+
score_threshold: exports_external.number().min(0.3).max(0.95).default(0.55),
|
|
14217
|
+
min_prompt_chars: exports_external.number().min(5).max(500).default(20)
|
|
14218
|
+
}).default({ enabled: false, score_threshold: 0.55, min_prompt_chars: 20 })
|
|
14209
14219
|
}).default({
|
|
14210
|
-
|
|
14211
|
-
|
|
14212
|
-
|
|
14220
|
+
temporal_awareness: false,
|
|
14221
|
+
git_commit_indexing: { enabled: false, since_days: 365, max_commits: 2000 },
|
|
14222
|
+
auto_search: { enabled: false, score_threshold: 0.55, min_prompt_chars: 20 }
|
|
14213
14223
|
}),
|
|
14214
14224
|
memory: exports_external.object({
|
|
14215
14225
|
enabled: exports_external.boolean().default(true),
|
|
@@ -14603,7 +14613,7 @@ function replaceSessionFacts(db, sessionId, facts) {
|
|
|
14603
14613
|
db.transaction(() => {
|
|
14604
14614
|
db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
|
|
14605
14615
|
insertFactRows(db, sessionId, facts, now);
|
|
14606
|
-
db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE session_id = ?").run(sessionId);
|
|
14616
|
+
db.prepare("UPDATE session_meta SET memory_block_cache = '', memory_block_ids = '' WHERE session_id = ?").run(sessionId);
|
|
14607
14617
|
})();
|
|
14608
14618
|
}
|
|
14609
14619
|
function getSessionFacts(db, sessionId) {
|
|
@@ -14617,7 +14627,7 @@ function replaceAllCompartmentState(db, sessionId, compartments, facts) {
|
|
|
14617
14627
|
db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
|
|
14618
14628
|
insertCompartmentRows(db, sessionId, compartments, now);
|
|
14619
14629
|
insertFactRows(db, sessionId, facts, now);
|
|
14620
|
-
db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE session_id = ?").run(sessionId);
|
|
14630
|
+
db.prepare("UPDATE session_meta SET memory_block_cache = '', memory_block_ids = '' WHERE session_id = ?").run(sessionId);
|
|
14621
14631
|
})();
|
|
14622
14632
|
}
|
|
14623
14633
|
function buildCompartmentBlock(compartments, facts, memoryBlock, dateRanges) {
|
|
@@ -14705,7 +14715,7 @@ function promoteRecompStaging(db, sessionId) {
|
|
|
14705
14715
|
}
|
|
14706
14716
|
function invalidateAllMemoryBlockCaches(db) {
|
|
14707
14717
|
try {
|
|
14708
|
-
db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE memory_block_cache != ''").run();
|
|
14718
|
+
db.prepare("UPDATE session_meta SET memory_block_cache = '', memory_block_ids = '' WHERE memory_block_cache != '' OR memory_block_ids != ''").run();
|
|
14709
14719
|
} catch {}
|
|
14710
14720
|
}
|
|
14711
14721
|
function clearRecompStaging(db, sessionId) {
|
|
@@ -16612,7 +16622,9 @@ class LocalEmbeddingProvider {
|
|
|
16612
16622
|
await this.initPromise;
|
|
16613
16623
|
return this.pipeline !== null;
|
|
16614
16624
|
}
|
|
16615
|
-
async embed(text) {
|
|
16625
|
+
async embed(text, signal) {
|
|
16626
|
+
if (signal?.aborted)
|
|
16627
|
+
return null;
|
|
16616
16628
|
if (!await this.initialize()) {
|
|
16617
16629
|
return null;
|
|
16618
16630
|
}
|
|
@@ -16631,10 +16643,13 @@ class LocalEmbeddingProvider {
|
|
|
16631
16643
|
return null;
|
|
16632
16644
|
}
|
|
16633
16645
|
}
|
|
16634
|
-
async embedBatch(texts) {
|
|
16646
|
+
async embedBatch(texts, signal) {
|
|
16635
16647
|
if (texts.length === 0) {
|
|
16636
16648
|
return [];
|
|
16637
16649
|
}
|
|
16650
|
+
if (signal?.aborted) {
|
|
16651
|
+
return Array.from({ length: texts.length }, () => null);
|
|
16652
|
+
}
|
|
16638
16653
|
if (!await this.initialize()) {
|
|
16639
16654
|
return Array.from({ length: texts.length }, () => null);
|
|
16640
16655
|
}
|
|
@@ -16695,6 +16710,10 @@ class OpenAICompatibleEmbeddingProvider {
|
|
|
16695
16710
|
model;
|
|
16696
16711
|
apiKey;
|
|
16697
16712
|
initialized = false;
|
|
16713
|
+
failureTimes = [];
|
|
16714
|
+
circuitOpenUntil = 0;
|
|
16715
|
+
openLogged = false;
|
|
16716
|
+
halfOpenProbeInFlight = false;
|
|
16698
16717
|
constructor(options) {
|
|
16699
16718
|
this.endpoint = normalizeEndpoint(options.endpoint);
|
|
16700
16719
|
this.model = options.model?.trim() ?? "";
|
|
@@ -16712,18 +16731,36 @@ class OpenAICompatibleEmbeddingProvider {
|
|
|
16712
16731
|
this.initialized = true;
|
|
16713
16732
|
return true;
|
|
16714
16733
|
}
|
|
16715
|
-
async embed(text) {
|
|
16716
|
-
const [embedding] = await this.embedBatch([text]);
|
|
16734
|
+
async embed(text, signal) {
|
|
16735
|
+
const [embedding] = await this.embedBatch([text], signal);
|
|
16717
16736
|
return embedding ?? null;
|
|
16718
16737
|
}
|
|
16719
|
-
async embedBatch(texts) {
|
|
16738
|
+
async embedBatch(texts, signal) {
|
|
16720
16739
|
if (texts.length === 0) {
|
|
16721
16740
|
return [];
|
|
16722
16741
|
}
|
|
16723
16742
|
if (!await this.initialize()) {
|
|
16724
16743
|
return Array.from({ length: texts.length }, () => null);
|
|
16725
16744
|
}
|
|
16745
|
+
if (signal?.aborted) {
|
|
16746
|
+
return Array.from({ length: texts.length }, () => null);
|
|
16747
|
+
}
|
|
16748
|
+
let isProbe = false;
|
|
16749
|
+
let internalController;
|
|
16750
|
+
let timeoutHandle;
|
|
16751
|
+
let onOuterAbort;
|
|
16726
16752
|
try {
|
|
16753
|
+
const claim = this.claimProbeOrShortCircuit();
|
|
16754
|
+
if (claim === "short_circuit") {
|
|
16755
|
+
return Array.from({ length: texts.length }, () => null);
|
|
16756
|
+
}
|
|
16757
|
+
isProbe = claim === "probe";
|
|
16758
|
+
internalController = new AbortController;
|
|
16759
|
+
timeoutHandle = setTimeout(() => internalController?.abort(), FETCH_TIMEOUT_MS);
|
|
16760
|
+
onOuterAbort = () => internalController?.abort();
|
|
16761
|
+
if (signal) {
|
|
16762
|
+
signal.addEventListener("abort", onOuterAbort, { once: true });
|
|
16763
|
+
}
|
|
16727
16764
|
const response = await fetch(`${this.endpoint}/embeddings`, {
|
|
16728
16765
|
method: "POST",
|
|
16729
16766
|
headers: {
|
|
@@ -16733,21 +16770,48 @@ class OpenAICompatibleEmbeddingProvider {
|
|
|
16733
16770
|
body: JSON.stringify({
|
|
16734
16771
|
model: this.model,
|
|
16735
16772
|
input: texts
|
|
16736
|
-
})
|
|
16773
|
+
}),
|
|
16774
|
+
signal: internalController.signal
|
|
16737
16775
|
});
|
|
16738
16776
|
if (!response.ok) {
|
|
16739
16777
|
log(`[magic-context] openai-compatible embedding request failed: ${response.status} ${response.statusText}`);
|
|
16778
|
+
this.recordFailure(isProbe);
|
|
16740
16779
|
return Array.from({ length: texts.length }, () => null);
|
|
16741
16780
|
}
|
|
16742
16781
|
const body = await response.json();
|
|
16743
16782
|
const items = Array.isArray(body.data) ? body.data : [];
|
|
16744
|
-
|
|
16783
|
+
const results = Array.from({ length: texts.length }, (_, index) => {
|
|
16745
16784
|
const embedding = items[index]?.embedding;
|
|
16746
16785
|
return Array.isArray(embedding) ? Float32Array.from(embedding) : null;
|
|
16747
16786
|
});
|
|
16787
|
+
if (results.every((r) => r === null)) {
|
|
16788
|
+
this.recordFailure(isProbe);
|
|
16789
|
+
} else {
|
|
16790
|
+
this.recordSuccess();
|
|
16791
|
+
}
|
|
16792
|
+
return results;
|
|
16748
16793
|
} catch (error48) {
|
|
16749
|
-
|
|
16794
|
+
const isAbort = error48 instanceof Error && (error48.name === "AbortError" || error48.message.includes("aborted"));
|
|
16795
|
+
if (isAbort) {
|
|
16796
|
+
if (signal?.aborted) {} else {
|
|
16797
|
+
log(`[magic-context] openai-compatible embedding request timed out after ${FETCH_TIMEOUT_MS}ms`);
|
|
16798
|
+
this.recordFailure(isProbe);
|
|
16799
|
+
}
|
|
16800
|
+
} else {
|
|
16801
|
+
log("[magic-context] openai-compatible embedding request failed:", error48);
|
|
16802
|
+
this.recordFailure(isProbe);
|
|
16803
|
+
}
|
|
16750
16804
|
return Array.from({ length: texts.length }, () => null);
|
|
16805
|
+
} finally {
|
|
16806
|
+
if (timeoutHandle !== undefined) {
|
|
16807
|
+
clearTimeout(timeoutHandle);
|
|
16808
|
+
}
|
|
16809
|
+
if (signal && onOuterAbort) {
|
|
16810
|
+
signal.removeEventListener("abort", onOuterAbort);
|
|
16811
|
+
}
|
|
16812
|
+
if (isProbe) {
|
|
16813
|
+
this.halfOpenProbeInFlight = false;
|
|
16814
|
+
}
|
|
16751
16815
|
}
|
|
16752
16816
|
}
|
|
16753
16817
|
async dispose() {
|
|
@@ -16756,9 +16820,73 @@ class OpenAICompatibleEmbeddingProvider {
|
|
|
16756
16820
|
isLoaded() {
|
|
16757
16821
|
return this.initialized;
|
|
16758
16822
|
}
|
|
16823
|
+
claimProbeOrShortCircuit() {
|
|
16824
|
+
if (this.circuitOpenUntil === 0) {
|
|
16825
|
+
return "allow";
|
|
16826
|
+
}
|
|
16827
|
+
if (Date.now() < this.circuitOpenUntil) {
|
|
16828
|
+
return "short_circuit";
|
|
16829
|
+
}
|
|
16830
|
+
if (this.halfOpenProbeInFlight) {
|
|
16831
|
+
return "short_circuit";
|
|
16832
|
+
}
|
|
16833
|
+
this.halfOpenProbeInFlight = true;
|
|
16834
|
+
log("[magic-context] openai-compatible embedding: circuit half-open, probing endpoint");
|
|
16835
|
+
return "probe";
|
|
16836
|
+
}
|
|
16837
|
+
recordFailure(isProbe) {
|
|
16838
|
+
if (isProbe) {
|
|
16839
|
+
this.circuitOpenUntil = Date.now() + OPEN_DURATION_MS;
|
|
16840
|
+
if (!this.openLogged) {
|
|
16841
|
+
log(`[magic-context] openai-compatible embedding: probe failed, re-opening circuit for ${OPEN_DURATION_MS / 60000}min`);
|
|
16842
|
+
this.openLogged = true;
|
|
16843
|
+
}
|
|
16844
|
+
this.failureTimes = [];
|
|
16845
|
+
return;
|
|
16846
|
+
}
|
|
16847
|
+
const now = Date.now();
|
|
16848
|
+
const cutoff = now - FAILURE_WINDOW_MS;
|
|
16849
|
+
this.failureTimes = this.failureTimes.filter((t) => t > cutoff);
|
|
16850
|
+
this.failureTimes.push(now);
|
|
16851
|
+
if (this.failureTimes.length >= FAILURE_THRESHOLD) {
|
|
16852
|
+
this.circuitOpenUntil = now + OPEN_DURATION_MS;
|
|
16853
|
+
if (!this.openLogged) {
|
|
16854
|
+
log(`[magic-context] openai-compatible embedding: opening circuit for ${OPEN_DURATION_MS / 60000}min after ${this.failureTimes.length} failures in ${FAILURE_WINDOW_MS / 1000}s`);
|
|
16855
|
+
this.openLogged = true;
|
|
16856
|
+
}
|
|
16857
|
+
this.failureTimes = [];
|
|
16858
|
+
}
|
|
16859
|
+
}
|
|
16860
|
+
recordSuccess() {
|
|
16861
|
+
if (this.failureTimes.length > 0 || this.circuitOpenUntil > 0 || this.openLogged) {
|
|
16862
|
+
log("[magic-context] openai-compatible embedding: endpoint recovered, circuit closed");
|
|
16863
|
+
}
|
|
16864
|
+
this.failureTimes = [];
|
|
16865
|
+
this.circuitOpenUntil = 0;
|
|
16866
|
+
this.openLogged = false;
|
|
16867
|
+
}
|
|
16868
|
+
_getCircuitState() {
|
|
16869
|
+
if (this.circuitOpenUntil === 0)
|
|
16870
|
+
return "closed";
|
|
16871
|
+
if (Date.now() < this.circuitOpenUntil) {
|
|
16872
|
+
return this.halfOpenProbeInFlight ? "half_open" : "open";
|
|
16873
|
+
}
|
|
16874
|
+
return "half_open";
|
|
16875
|
+
}
|
|
16876
|
+
_getFailureCount() {
|
|
16877
|
+
return this.failureTimes.length;
|
|
16878
|
+
}
|
|
16879
|
+
_resetCircuit() {
|
|
16880
|
+
this.failureTimes = [];
|
|
16881
|
+
this.circuitOpenUntil = 0;
|
|
16882
|
+
this.openLogged = false;
|
|
16883
|
+
this.halfOpenProbeInFlight = false;
|
|
16884
|
+
}
|
|
16759
16885
|
}
|
|
16886
|
+
var FAILURE_THRESHOLD = 3, FAILURE_WINDOW_MS = 60000, OPEN_DURATION_MS, FETCH_TIMEOUT_MS = 30000;
|
|
16760
16887
|
var init_embedding_openai = __esm(() => {
|
|
16761
16888
|
init_logger();
|
|
16889
|
+
OPEN_DURATION_MS = 5 * 60000;
|
|
16762
16890
|
});
|
|
16763
16891
|
|
|
16764
16892
|
// src/features/magic-context/memory/embedding.ts
|
|
@@ -16847,7 +16975,7 @@ function initializeEmbedding(config2) {
|
|
|
16847
16975
|
function isEmbeddingEnabled() {
|
|
16848
16976
|
return embeddingConfig.provider !== "off";
|
|
16849
16977
|
}
|
|
16850
|
-
async function embedText(text) {
|
|
16978
|
+
async function embedText(text, signal) {
|
|
16851
16979
|
const currentProvider = getOrCreateProvider();
|
|
16852
16980
|
if (!currentProvider) {
|
|
16853
16981
|
return null;
|
|
@@ -16855,9 +16983,9 @@ async function embedText(text) {
|
|
|
16855
16983
|
if (!await currentProvider.initialize()) {
|
|
16856
16984
|
return null;
|
|
16857
16985
|
}
|
|
16858
|
-
return currentProvider.embed(text);
|
|
16986
|
+
return currentProvider.embed(text, signal);
|
|
16859
16987
|
}
|
|
16860
|
-
async function embedBatch(texts) {
|
|
16988
|
+
async function embedBatch(texts, signal) {
|
|
16861
16989
|
if (texts.length === 0) {
|
|
16862
16990
|
return [];
|
|
16863
16991
|
}
|
|
@@ -16868,22 +16996,55 @@ async function embedBatch(texts) {
|
|
|
16868
16996
|
if (!await currentProvider.initialize()) {
|
|
16869
16997
|
return Array.from({ length: texts.length }, () => null);
|
|
16870
16998
|
}
|
|
16871
|
-
return currentProvider.embedBatch(texts);
|
|
16999
|
+
return currentProvider.embedBatch(texts, signal);
|
|
16872
17000
|
}
|
|
16873
17001
|
async function embedAllUnembeddedMemories(db, config2, batchSize = 10) {
|
|
16874
|
-
|
|
16875
|
-
|
|
17002
|
+
if (sweepInProgress) {
|
|
17003
|
+
log("[magic-context] embedding sweep already in progress, skipping this tick");
|
|
16876
17004
|
return 0;
|
|
16877
|
-
const projects = db.prepare(`SELECT DISTINCT m.project_path FROM memories m
|
|
16878
|
-
WHERE m.status IN ('active', 'permanent')
|
|
16879
|
-
AND m.id NOT IN (SELECT memory_id FROM memory_embeddings)
|
|
16880
|
-
LIMIT 20`).all();
|
|
16881
|
-
let total = 0;
|
|
16882
|
-
for (const row of projects) {
|
|
16883
|
-
const count = await embedUnembeddedMemoriesForProject(db, row.project_path, config2, batchSize);
|
|
16884
|
-
total += count;
|
|
16885
17005
|
}
|
|
16886
|
-
|
|
17006
|
+
sweepInProgress = true;
|
|
17007
|
+
const startedAt = Date.now();
|
|
17008
|
+
const deadline = startedAt + SWEEP_MAX_WALL_CLOCK_MS;
|
|
17009
|
+
try {
|
|
17010
|
+
const resolvedConfig = resolveEmbeddingConfig(config2);
|
|
17011
|
+
if (resolvedConfig.provider === "off")
|
|
17012
|
+
return 0;
|
|
17013
|
+
const projects = db.prepare(`SELECT m.project_path, MAX(m.updated_at) AS latest
|
|
17014
|
+
FROM memories m
|
|
17015
|
+
WHERE m.status IN ('active', 'permanent')
|
|
17016
|
+
AND m.id NOT IN (SELECT memory_id FROM memory_embeddings)
|
|
17017
|
+
GROUP BY m.project_path
|
|
17018
|
+
ORDER BY latest DESC
|
|
17019
|
+
LIMIT 20`).all();
|
|
17020
|
+
let total = 0;
|
|
17021
|
+
let consecutiveEmpty = 0;
|
|
17022
|
+
outer:
|
|
17023
|
+
for (const project of projects) {
|
|
17024
|
+
while (Date.now() < deadline) {
|
|
17025
|
+
const count = await embedUnembeddedMemoriesForProject(db, project.project_path, config2, batchSize);
|
|
17026
|
+
if (count === 0) {
|
|
17027
|
+
consecutiveEmpty += 1;
|
|
17028
|
+
if (consecutiveEmpty >= SWEEP_MAX_CONSECUTIVE_EMPTY) {
|
|
17029
|
+
log(`[magic-context] embedding sweep: ${SWEEP_MAX_CONSECUTIVE_EMPTY} consecutive empty batches, stopping (total=${total})`);
|
|
17030
|
+
break outer;
|
|
17031
|
+
}
|
|
17032
|
+
break;
|
|
17033
|
+
}
|
|
17034
|
+
consecutiveEmpty = 0;
|
|
17035
|
+
total += count;
|
|
17036
|
+
if (count < batchSize)
|
|
17037
|
+
break;
|
|
17038
|
+
}
|
|
17039
|
+
if (Date.now() >= deadline) {
|
|
17040
|
+
log(`[magic-context] embedding sweep: wall-clock deadline reached after ${((Date.now() - startedAt) / 1000).toFixed(1)}s (total=${total})`);
|
|
17041
|
+
break;
|
|
17042
|
+
}
|
|
17043
|
+
}
|
|
17044
|
+
return total;
|
|
17045
|
+
} finally {
|
|
17046
|
+
sweepInProgress = false;
|
|
17047
|
+
}
|
|
16887
17048
|
}
|
|
16888
17049
|
async function embedUnembeddedMemoriesForProject(db, projectPath, config2, batchSize = 10) {
|
|
16889
17050
|
const normalizedBatchSize = Math.max(1, Math.floor(batchSize));
|
|
@@ -16922,7 +17083,7 @@ async function embedUnembeddedMemoriesForProject(db, projectPath, config2, batch
|
|
|
16922
17083
|
function getEmbeddingModelId() {
|
|
16923
17084
|
return getOrCreateProvider()?.modelId ?? "off";
|
|
16924
17085
|
}
|
|
16925
|
-
var DEFAULT_EMBEDDING_CONFIG, embeddingConfig, provider = null, loadUnembeddedMemoriesStatements;
|
|
17086
|
+
var DEFAULT_EMBEDDING_CONFIG, embeddingConfig, provider = null, loadUnembeddedMemoriesStatements, SWEEP_MAX_WALL_CLOCK_MS, SWEEP_MAX_CONSECUTIVE_EMPTY = 3, sweepInProgress = false;
|
|
16926
17087
|
var init_embedding = __esm(() => {
|
|
16927
17088
|
init_magic_context();
|
|
16928
17089
|
init_logger();
|
|
@@ -16935,6 +17096,80 @@ var init_embedding = __esm(() => {
|
|
|
16935
17096
|
};
|
|
16936
17097
|
embeddingConfig = DEFAULT_EMBEDDING_CONFIG;
|
|
16937
17098
|
loadUnembeddedMemoriesStatements = new WeakMap;
|
|
17099
|
+
SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
|
|
17100
|
+
});
|
|
17101
|
+
|
|
17102
|
+
// src/features/magic-context/memory/storage-memory-fts.ts
|
|
17103
|
+
function getSearchStatement(db) {
|
|
17104
|
+
let stmt = searchStatements.get(db);
|
|
17105
|
+
if (!stmt) {
|
|
17106
|
+
const selectColumns = Object.entries(COLUMN_MAP).map(([property, column]) => `memories.${column} AS ${property}`).join(", ");
|
|
17107
|
+
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 ?`);
|
|
17108
|
+
searchStatements.set(db, stmt);
|
|
17109
|
+
}
|
|
17110
|
+
return stmt;
|
|
17111
|
+
}
|
|
17112
|
+
function sanitizeFtsQuery(query) {
|
|
17113
|
+
const tokens = query.split(/\s+/).filter((token) => token.length > 0);
|
|
17114
|
+
if (tokens.length === 0)
|
|
17115
|
+
return "";
|
|
17116
|
+
return tokens.map((token) => `"${token.replace(/"/g, '""')}"`).join(" ");
|
|
17117
|
+
}
|
|
17118
|
+
function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT) {
|
|
17119
|
+
const trimmedQuery = query.trim();
|
|
17120
|
+
if (trimmedQuery.length === 0 || limit <= 0) {
|
|
17121
|
+
return [];
|
|
17122
|
+
}
|
|
17123
|
+
const sanitized = sanitizeFtsQuery(trimmedQuery);
|
|
17124
|
+
if (sanitized.length === 0) {
|
|
17125
|
+
return [];
|
|
17126
|
+
}
|
|
17127
|
+
const rows = getSearchStatement(db).all(projectPath, Date.now(), sanitized, limit).filter(isMemoryRow);
|
|
17128
|
+
return rows.map(toMemory);
|
|
17129
|
+
}
|
|
17130
|
+
var DEFAULT_SEARCH_LIMIT = 10, searchStatements;
|
|
17131
|
+
var init_storage_memory_fts = __esm(() => {
|
|
17132
|
+
init_storage_memory();
|
|
17133
|
+
searchStatements = new WeakMap;
|
|
17134
|
+
});
|
|
17135
|
+
|
|
17136
|
+
// src/features/magic-context/memory/project-identity.ts
|
|
17137
|
+
import { execSync } from "child_process";
|
|
17138
|
+
import path3 from "path";
|
|
17139
|
+
function getRootCommitHash(directory) {
|
|
17140
|
+
try {
|
|
17141
|
+
const hash2 = execSync("git rev-list --max-parents=0 HEAD", {
|
|
17142
|
+
cwd: directory,
|
|
17143
|
+
encoding: "utf-8",
|
|
17144
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
17145
|
+
timeout: GIT_TIMEOUT_MS2
|
|
17146
|
+
}).trim();
|
|
17147
|
+
const firstLine = hash2.split(`
|
|
17148
|
+
`)[0]?.trim();
|
|
17149
|
+
return firstLine && firstLine.length >= 7 ? firstLine : undefined;
|
|
17150
|
+
} catch {
|
|
17151
|
+
return;
|
|
17152
|
+
}
|
|
17153
|
+
}
|
|
17154
|
+
function directoryFallback(directory) {
|
|
17155
|
+
const canonical = path3.resolve(directory);
|
|
17156
|
+
const hash2 = Bun.hash(canonical).toString(16).slice(0, 12);
|
|
17157
|
+
return `dir:${hash2}`;
|
|
17158
|
+
}
|
|
17159
|
+
function resolveProjectIdentity(directory) {
|
|
17160
|
+
const resolved = path3.resolve(directory);
|
|
17161
|
+
const cached2 = resolvedCache.get(resolved);
|
|
17162
|
+
if (cached2 !== undefined) {
|
|
17163
|
+
return cached2;
|
|
17164
|
+
}
|
|
17165
|
+
const rootHash = getRootCommitHash(resolved);
|
|
17166
|
+
const identity = rootHash ? `git:${rootHash}` : directoryFallback(resolved);
|
|
17167
|
+
resolvedCache.set(resolved, identity);
|
|
17168
|
+
return identity;
|
|
17169
|
+
}
|
|
17170
|
+
var GIT_TIMEOUT_MS2 = 5000, resolvedCache;
|
|
17171
|
+
var init_project_identity = __esm(() => {
|
|
17172
|
+
resolvedCache = new Map;
|
|
16938
17173
|
});
|
|
16939
17174
|
|
|
16940
17175
|
// src/features/magic-context/compression-depth-storage.ts
|
|
@@ -150193,6 +150428,62 @@ var init_migrations = __esm(() => {
|
|
|
150193
150428
|
CREATE INDEX IF NOT EXISTS idx_um_status ON user_memories(status);
|
|
150194
150429
|
`);
|
|
150195
150430
|
}
|
|
150431
|
+
},
|
|
150432
|
+
{
|
|
150433
|
+
version: 4,
|
|
150434
|
+
description: "Add git_commits + git_commit_embeddings + git_commits_fts tables",
|
|
150435
|
+
up: (db) => {
|
|
150436
|
+
db.exec(`
|
|
150437
|
+
CREATE TABLE IF NOT EXISTS git_commits (
|
|
150438
|
+
sha TEXT PRIMARY KEY,
|
|
150439
|
+
project_path TEXT NOT NULL,
|
|
150440
|
+
short_sha TEXT NOT NULL,
|
|
150441
|
+
message TEXT NOT NULL,
|
|
150442
|
+
author TEXT,
|
|
150443
|
+
committed_at INTEGER NOT NULL,
|
|
150444
|
+
indexed_at INTEGER NOT NULL
|
|
150445
|
+
);
|
|
150446
|
+
CREATE INDEX IF NOT EXISTS idx_git_commits_project_time
|
|
150447
|
+
ON git_commits(project_path, committed_at DESC);
|
|
150448
|
+
|
|
150449
|
+
CREATE TABLE IF NOT EXISTS git_commit_embeddings (
|
|
150450
|
+
sha TEXT PRIMARY KEY,
|
|
150451
|
+
embedding BLOB NOT NULL,
|
|
150452
|
+
model_id TEXT NOT NULL,
|
|
150453
|
+
created_at INTEGER NOT NULL,
|
|
150454
|
+
FOREIGN KEY(sha) REFERENCES git_commits(sha) ON DELETE CASCADE
|
|
150455
|
+
);
|
|
150456
|
+
|
|
150457
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS git_commits_fts USING fts5(
|
|
150458
|
+
sha UNINDEXED,
|
|
150459
|
+
project_path UNINDEXED,
|
|
150460
|
+
message,
|
|
150461
|
+
tokenize = 'porter unicode61'
|
|
150462
|
+
);
|
|
150463
|
+
|
|
150464
|
+
-- Mirror writes into FTS. We intentionally rebuild FTS rows on
|
|
150465
|
+
-- every INSERT OR REPLACE so amended commits or re-indexed
|
|
150466
|
+
-- messages update cleanly.
|
|
150467
|
+
CREATE TRIGGER IF NOT EXISTS git_commits_fts_insert
|
|
150468
|
+
AFTER INSERT ON git_commits BEGIN
|
|
150469
|
+
DELETE FROM git_commits_fts WHERE sha = NEW.sha;
|
|
150470
|
+
INSERT INTO git_commits_fts(sha, project_path, message)
|
|
150471
|
+
VALUES (NEW.sha, NEW.project_path, NEW.message);
|
|
150472
|
+
END;
|
|
150473
|
+
|
|
150474
|
+
CREATE TRIGGER IF NOT EXISTS git_commits_fts_delete
|
|
150475
|
+
AFTER DELETE ON git_commits BEGIN
|
|
150476
|
+
DELETE FROM git_commits_fts WHERE sha = OLD.sha;
|
|
150477
|
+
END;
|
|
150478
|
+
|
|
150479
|
+
CREATE TRIGGER IF NOT EXISTS git_commits_fts_update
|
|
150480
|
+
AFTER UPDATE OF message, project_path ON git_commits BEGIN
|
|
150481
|
+
DELETE FROM git_commits_fts WHERE sha = OLD.sha;
|
|
150482
|
+
INSERT INTO git_commits_fts(sha, project_path, message)
|
|
150483
|
+
VALUES (NEW.sha, NEW.project_path, NEW.message);
|
|
150484
|
+
END;
|
|
150485
|
+
`);
|
|
150486
|
+
}
|
|
150196
150487
|
}
|
|
150197
150488
|
];
|
|
150198
150489
|
});
|
|
@@ -150415,7 +150706,8 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
|
|
|
150415
150706
|
historian_last_failure_at INTEGER DEFAULT NULL,
|
|
150416
150707
|
system_prompt_hash TEXT DEFAULT '',
|
|
150417
150708
|
memory_block_cache TEXT DEFAULT '',
|
|
150418
|
-
memory_block_count INTEGER DEFAULT 0
|
|
150709
|
+
memory_block_count INTEGER DEFAULT 0,
|
|
150710
|
+
memory_block_ids TEXT DEFAULT ''
|
|
150419
150711
|
);
|
|
150420
150712
|
|
|
150421
150713
|
CREATE INDEX IF NOT EXISTS idx_tags_session_tag_number ON tags(session_id, tag_number);
|
|
@@ -150466,6 +150758,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
|
|
|
150466
150758
|
ensureColumn(db, "session_meta", "note_nudge_trigger_message_id", "TEXT DEFAULT ''");
|
|
150467
150759
|
ensureColumn(db, "session_meta", "note_nudge_sticky_text", "TEXT DEFAULT ''");
|
|
150468
150760
|
ensureColumn(db, "session_meta", "note_nudge_sticky_message_id", "TEXT DEFAULT ''");
|
|
150761
|
+
ensureColumn(db, "session_meta", "note_last_read_at", "INTEGER DEFAULT 0");
|
|
150469
150762
|
ensureColumn(db, "session_meta", "times_execute_threshold_reached", "INTEGER DEFAULT 0");
|
|
150470
150763
|
ensureColumn(db, "session_meta", "compartment_in_progress", "INTEGER DEFAULT 0");
|
|
150471
150764
|
ensureColumn(db, "session_meta", "historian_failure_count", "INTEGER DEFAULT 0");
|
|
@@ -150479,6 +150772,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
|
|
|
150479
150772
|
ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
|
|
150480
150773
|
ensureColumn(db, "session_meta", "memory_block_cache", "TEXT DEFAULT ''");
|
|
150481
150774
|
ensureColumn(db, "session_meta", "memory_block_count", "INTEGER DEFAULT 0");
|
|
150775
|
+
ensureColumn(db, "session_meta", "memory_block_ids", "TEXT DEFAULT ''");
|
|
150482
150776
|
ensureColumn(db, "dream_queue", "retry_count", "INTEGER DEFAULT 0");
|
|
150483
150777
|
ensureColumn(db, "tags", "reasoning_byte_size", "INTEGER DEFAULT 0");
|
|
150484
150778
|
ensureColumn(db, "tags", "drop_mode", "TEXT DEFAULT 'full'");
|
|
@@ -150491,8 +150785,16 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
|
|
|
150491
150785
|
ensureColumn(db, "session_meta", "tool_call_tokens", "INTEGER DEFAULT 0");
|
|
150492
150786
|
ensureColumn(db, "session_meta", "recomp_partial_range_start", "INTEGER DEFAULT 0");
|
|
150493
150787
|
ensureColumn(db, "session_meta", "recomp_partial_range_end", "INTEGER DEFAULT 0");
|
|
150788
|
+
ensureColumn(db, "session_meta", "detected_context_limit", "INTEGER DEFAULT 0");
|
|
150789
|
+
ensureColumn(db, "session_meta", "needs_emergency_recovery", "INTEGER DEFAULT 0");
|
|
150494
150790
|
healNullTextColumns(db);
|
|
150495
150791
|
healNullIntegerColumns(db);
|
|
150792
|
+
healMissingMemoryBlockIds(db);
|
|
150793
|
+
}
|
|
150794
|
+
function healMissingMemoryBlockIds(db) {
|
|
150795
|
+
try {
|
|
150796
|
+
db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE memory_block_cache != '' AND (memory_block_ids IS NULL OR memory_block_ids = '') AND memory_block_count > 0").run();
|
|
150797
|
+
} catch {}
|
|
150496
150798
|
}
|
|
150497
150799
|
function healNullTextColumns(db) {
|
|
150498
150800
|
const columns = [
|
|
@@ -150509,6 +150811,7 @@ function healNullTextColumns(db) {
|
|
|
150509
150811
|
["system_prompt_hash", ""],
|
|
150510
150812
|
["stripped_placeholder_ids", ""],
|
|
150511
150813
|
["memory_block_cache", ""],
|
|
150814
|
+
["memory_block_ids", ""],
|
|
150512
150815
|
["compaction_marker_state", ""],
|
|
150513
150816
|
["key_files", ""]
|
|
150514
150817
|
];
|
|
@@ -150768,6 +151071,9 @@ function setPersistedReasoningWatermark(db, sessionId, tagNumber) {
|
|
|
150768
151071
|
ensureSessionMetaRow(db, sessionId);
|
|
150769
151072
|
db.prepare("UPDATE session_meta SET cleared_reasoning_through_tag = ? WHERE session_id = ?").run(tagNumber, sessionId);
|
|
150770
151073
|
}
|
|
151074
|
+
function clearPersistedReasoningWatermark(db, sessionId) {
|
|
151075
|
+
setPersistedReasoningWatermark(db, sessionId, 0);
|
|
151076
|
+
}
|
|
150771
151077
|
function getPersistedNudgePlacement(db, sessionId) {
|
|
150772
151078
|
const result = db.prepare("SELECT nudge_anchor_message_id, nudge_anchor_text FROM session_meta WHERE session_id = ?").get(sessionId);
|
|
150773
151079
|
if (!isPersistedNudgePlacementRow(result)) {
|
|
@@ -150845,6 +151151,23 @@ function setPersistedDeliveredNoteNudge(db, sessionId, text, messageId = "") {
|
|
|
150845
151151
|
function clearPersistedNoteNudge(db, sessionId) {
|
|
150846
151152
|
db.prepare("UPDATE session_meta SET note_nudge_trigger_pending = 0, note_nudge_trigger_message_id = '', note_nudge_sticky_text = '', note_nudge_sticky_message_id = '' WHERE session_id = ?").run(sessionId);
|
|
150847
151153
|
}
|
|
151154
|
+
function getNoteLastReadAt(db, sessionId) {
|
|
151155
|
+
try {
|
|
151156
|
+
const result = db.prepare("SELECT note_last_read_at FROM session_meta WHERE session_id = ?").get(sessionId);
|
|
151157
|
+
if (!result || typeof result !== "object")
|
|
151158
|
+
return 0;
|
|
151159
|
+
const value = result.note_last_read_at;
|
|
151160
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
151161
|
+
} catch {
|
|
151162
|
+
return 0;
|
|
151163
|
+
}
|
|
151164
|
+
}
|
|
151165
|
+
function setNoteLastReadAt(db, sessionId, at = Date.now()) {
|
|
151166
|
+
db.transaction(() => {
|
|
151167
|
+
ensureSessionMetaRow(db, sessionId);
|
|
151168
|
+
db.prepare("UPDATE session_meta SET note_last_read_at = ? WHERE session_id = ?").run(at, sessionId);
|
|
151169
|
+
})();
|
|
151170
|
+
}
|
|
150848
151171
|
function getHistorianFailureState(db, sessionId) {
|
|
150849
151172
|
const result = db.prepare("SELECT historian_failure_count, historian_last_error, historian_last_failure_at FROM session_meta WHERE session_id = ?").get(sessionId);
|
|
150850
151173
|
if (!isPersistedHistorianFailureRow(result)) {
|
|
@@ -150869,6 +151192,37 @@ function clearHistorianFailureState(db, sessionId) {
|
|
|
150869
151192
|
db.prepare("UPDATE session_meta SET historian_failure_count = 0, historian_last_error = NULL, historian_last_failure_at = NULL WHERE session_id = ?").run(sessionId);
|
|
150870
151193
|
})();
|
|
150871
151194
|
}
|
|
151195
|
+
function getOverflowState(db, sessionId) {
|
|
151196
|
+
const result = db.prepare("SELECT detected_context_limit, needs_emergency_recovery FROM session_meta WHERE session_id = ?").get(sessionId);
|
|
151197
|
+
if (!result) {
|
|
151198
|
+
return { detectedContextLimit: 0, needsEmergencyRecovery: false };
|
|
151199
|
+
}
|
|
151200
|
+
const limit = typeof result.detected_context_limit === "number" && result.detected_context_limit > 0 ? result.detected_context_limit : 0;
|
|
151201
|
+
const needs = typeof result.needs_emergency_recovery === "number" && result.needs_emergency_recovery > 0;
|
|
151202
|
+
return { detectedContextLimit: limit, needsEmergencyRecovery: needs };
|
|
151203
|
+
}
|
|
151204
|
+
function recordOverflowDetected(db, sessionId, reportedLimit) {
|
|
151205
|
+
db.transaction(() => {
|
|
151206
|
+
ensureSessionMetaRow(db, sessionId);
|
|
151207
|
+
if (typeof reportedLimit === "number" && reportedLimit > 0) {
|
|
151208
|
+
db.prepare("UPDATE session_meta SET detected_context_limit = ?, needs_emergency_recovery = 1 WHERE session_id = ?").run(reportedLimit, sessionId);
|
|
151209
|
+
} else {
|
|
151210
|
+
db.prepare("UPDATE session_meta SET needs_emergency_recovery = 1 WHERE session_id = ?").run(sessionId);
|
|
151211
|
+
}
|
|
151212
|
+
})();
|
|
151213
|
+
}
|
|
151214
|
+
function clearEmergencyRecovery(db, sessionId) {
|
|
151215
|
+
db.transaction(() => {
|
|
151216
|
+
ensureSessionMetaRow(db, sessionId);
|
|
151217
|
+
db.prepare("UPDATE session_meta SET needs_emergency_recovery = 0 WHERE session_id = ?").run(sessionId);
|
|
151218
|
+
})();
|
|
151219
|
+
}
|
|
151220
|
+
function clearDetectedContextLimit(db, sessionId) {
|
|
151221
|
+
db.transaction(() => {
|
|
151222
|
+
ensureSessionMetaRow(db, sessionId);
|
|
151223
|
+
db.prepare("UPDATE session_meta SET detected_context_limit = 0 WHERE session_id = ?").run(sessionId);
|
|
151224
|
+
})();
|
|
151225
|
+
}
|
|
150872
151226
|
function getPersistedCompactionMarkerState(db, sessionId) {
|
|
150873
151227
|
const row = db.prepare("SELECT compaction_marker_state FROM session_meta WHERE session_id = ?").get(sessionId);
|
|
150874
151228
|
const raw = row?.compaction_marker_state;
|
|
@@ -151274,8 +151628,33 @@ function resolveLimit(limit) {
|
|
|
151274
151628
|
return limit.context;
|
|
151275
151629
|
return;
|
|
151276
151630
|
}
|
|
151277
|
-
function
|
|
151278
|
-
|
|
151631
|
+
function resolveInterleavedField(interleaved) {
|
|
151632
|
+
if (interleaved && typeof interleaved === "object" && typeof interleaved.field === "string" && interleaved.field.length > 0) {
|
|
151633
|
+
return interleaved.field;
|
|
151634
|
+
}
|
|
151635
|
+
return;
|
|
151636
|
+
}
|
|
151637
|
+
function setCachedModelMetadata(cache, key, model) {
|
|
151638
|
+
const limit = resolveLimit(model?.limit);
|
|
151639
|
+
const interleavedField = resolveInterleavedField(model?.capabilities?.interleaved) ?? resolveInterleavedField(model?.interleaved);
|
|
151640
|
+
if (limit === undefined && interleavedField === undefined) {
|
|
151641
|
+
return;
|
|
151642
|
+
}
|
|
151643
|
+
const value = {};
|
|
151644
|
+
if (limit !== undefined)
|
|
151645
|
+
value.limit = limit;
|
|
151646
|
+
if (interleavedField !== undefined)
|
|
151647
|
+
value.interleavedField = interleavedField;
|
|
151648
|
+
cache.set(key, value);
|
|
151649
|
+
const modes = model?.experimental?.modes;
|
|
151650
|
+
if (modes && typeof modes === "object") {
|
|
151651
|
+
for (const mode of Object.keys(modes)) {
|
|
151652
|
+
cache.set(`${key}-${mode}`, value);
|
|
151653
|
+
}
|
|
151654
|
+
}
|
|
151655
|
+
}
|
|
151656
|
+
function loadModelsDevMetadataFromFile() {
|
|
151657
|
+
const metadata = new Map;
|
|
151279
151658
|
const modelsJsonPath = getModelsJsonPath();
|
|
151280
151659
|
let fileFound = false;
|
|
151281
151660
|
try {
|
|
@@ -151287,16 +151666,7 @@ function loadModelsDevLimitsFromFile() {
|
|
|
151287
151666
|
if (!provider2?.models || typeof provider2.models !== "object")
|
|
151288
151667
|
continue;
|
|
151289
151668
|
for (const [modelId, model] of Object.entries(provider2.models)) {
|
|
151290
|
-
|
|
151291
|
-
if (typeof effective === "number" && effective > 0) {
|
|
151292
|
-
limits.set(`${providerId}/${modelId}`, effective);
|
|
151293
|
-
const modes = model?.experimental?.modes;
|
|
151294
|
-
if (modes && typeof modes === "object") {
|
|
151295
|
-
for (const mode of Object.keys(modes)) {
|
|
151296
|
-
limits.set(`${providerId}/${modelId}-${mode}`, effective);
|
|
151297
|
-
}
|
|
151298
|
-
}
|
|
151299
|
-
}
|
|
151669
|
+
setCachedModelMetadata(metadata, `${providerId}/${modelId}`, model);
|
|
151300
151670
|
}
|
|
151301
151671
|
}
|
|
151302
151672
|
}
|
|
@@ -151314,10 +151684,7 @@ function loadModelsDevLimitsFromFile() {
|
|
|
151314
151684
|
if (!provider2?.models || typeof provider2.models !== "object")
|
|
151315
151685
|
continue;
|
|
151316
151686
|
for (const [modelId, model] of Object.entries(provider2.models)) {
|
|
151317
|
-
|
|
151318
|
-
if (typeof effective === "number" && effective > 0) {
|
|
151319
|
-
limits.set(`${providerId}/${modelId}`, effective);
|
|
151320
|
-
}
|
|
151687
|
+
setCachedModelMetadata(metadata, `${providerId}/${modelId}`, model);
|
|
151321
151688
|
}
|
|
151322
151689
|
}
|
|
151323
151690
|
}
|
|
@@ -151325,8 +151692,8 @@ function loadModelsDevLimitsFromFile() {
|
|
|
151325
151692
|
} catch (error48) {
|
|
151326
151693
|
sessionLog("global", "models-dev-cache: failed to read opencode config for custom models:", error48 instanceof Error ? error48.message : String(error48));
|
|
151327
151694
|
}
|
|
151328
|
-
sessionLog("global", `models-dev-cache: file-layer loaded ${
|
|
151329
|
-
return
|
|
151695
|
+
sessionLog("global", `models-dev-cache: file-layer loaded ${metadata.size} model metadata entries (modelsJsonPath=${modelsJsonPath}, found=${fileFound})`);
|
|
151696
|
+
return metadata;
|
|
151330
151697
|
}
|
|
151331
151698
|
async function refreshModelLimitsFromApi(client) {
|
|
151332
151699
|
try {
|
|
@@ -151343,17 +151710,14 @@ async function refreshModelLimitsFromApi(client) {
|
|
|
151343
151710
|
if (!p?.id || !p.models || typeof p.models !== "object")
|
|
151344
151711
|
continue;
|
|
151345
151712
|
for (const [modelId, model] of Object.entries(p.models)) {
|
|
151346
|
-
|
|
151347
|
-
if (typeof effective === "number" && effective > 0) {
|
|
151348
|
-
map2.set(`${p.id}/${modelId}`, effective);
|
|
151349
|
-
}
|
|
151713
|
+
setCachedModelMetadata(map2, `${p.id}/${modelId}`, model);
|
|
151350
151714
|
}
|
|
151351
151715
|
}
|
|
151352
151716
|
const previousSize = apiCache?.size ?? null;
|
|
151353
151717
|
apiCache = map2;
|
|
151354
151718
|
apiLoadedAt = Date.now();
|
|
151355
151719
|
if (previousSize === null || previousSize !== map2.size) {
|
|
151356
|
-
sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model
|
|
151720
|
+
sessionLog("global", `models-dev-cache: API layer loaded ${map2.size} model metadata entries${previousSize !== null ? ` (was ${previousSize})` : ""}`);
|
|
151357
151721
|
}
|
|
151358
151722
|
} catch (error48) {
|
|
151359
151723
|
sessionLog("global", "models-dev-cache: API refresh failed:", error48 instanceof Error ? error48.message : String(error48));
|
|
@@ -151362,16 +151726,32 @@ async function refreshModelLimitsFromApi(client) {
|
|
|
151362
151726
|
function getModelsDevContextLimit(providerID, modelID) {
|
|
151363
151727
|
const key = `${providerID}/${modelID}`;
|
|
151364
151728
|
if (apiCache) {
|
|
151365
|
-
const fromApi = apiCache.get(key);
|
|
151729
|
+
const fromApi = apiCache.get(key)?.limit;
|
|
151366
151730
|
if (typeof fromApi === "number")
|
|
151367
151731
|
return fromApi;
|
|
151368
151732
|
}
|
|
151369
151733
|
const now = Date.now();
|
|
151370
151734
|
if (!fileCache || now - fileLastAttempt > RELOAD_INTERVAL_MS) {
|
|
151371
151735
|
fileLastAttempt = now;
|
|
151372
|
-
fileCache =
|
|
151736
|
+
fileCache = loadModelsDevMetadataFromFile();
|
|
151737
|
+
}
|
|
151738
|
+
return fileCache.get(key)?.limit;
|
|
151739
|
+
}
|
|
151740
|
+
function getModelsDevInterleavedField(providerID, modelID) {
|
|
151741
|
+
const key = `${providerID}/${modelID}`;
|
|
151742
|
+
if (apiCache) {
|
|
151743
|
+
const fromApi = apiCache.get(key)?.interleavedField;
|
|
151744
|
+
if (typeof fromApi === "string" && fromApi.length > 0) {
|
|
151745
|
+
return fromApi;
|
|
151746
|
+
}
|
|
151747
|
+
}
|
|
151748
|
+
const now = Date.now();
|
|
151749
|
+
if (!fileCache || now - fileLastAttempt > RELOAD_INTERVAL_MS) {
|
|
151750
|
+
fileLastAttempt = now;
|
|
151751
|
+
fileCache = loadModelsDevMetadataFromFile();
|
|
151373
151752
|
}
|
|
151374
|
-
|
|
151753
|
+
const fromFile = fileCache.get(key)?.interleavedField;
|
|
151754
|
+
return typeof fromFile === "string" && fromFile.length > 0 ? fromFile : undefined;
|
|
151375
151755
|
}
|
|
151376
151756
|
var RELOAD_INTERVAL_MS, apiCache = null, apiLoadedAt = 0, fileCache = null, fileLastAttempt = 0;
|
|
151377
151757
|
var init_models_dev_cache = __esm(() => {
|
|
@@ -151379,45 +151759,6 @@ var init_models_dev_cache = __esm(() => {
|
|
|
151379
151759
|
RELOAD_INTERVAL_MS = 5 * 60 * 1000;
|
|
151380
151760
|
});
|
|
151381
151761
|
|
|
151382
|
-
// src/features/magic-context/memory/project-identity.ts
|
|
151383
|
-
import { execSync } from "child_process";
|
|
151384
|
-
import path3 from "path";
|
|
151385
|
-
function getRootCommitHash(directory) {
|
|
151386
|
-
try {
|
|
151387
|
-
const hash2 = execSync("git rev-list --max-parents=0 HEAD", {
|
|
151388
|
-
cwd: directory,
|
|
151389
|
-
encoding: "utf-8",
|
|
151390
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
151391
|
-
timeout: GIT_TIMEOUT_MS
|
|
151392
|
-
}).trim();
|
|
151393
|
-
const firstLine = hash2.split(`
|
|
151394
|
-
`)[0]?.trim();
|
|
151395
|
-
return firstLine && firstLine.length >= 7 ? firstLine : undefined;
|
|
151396
|
-
} catch {
|
|
151397
|
-
return;
|
|
151398
|
-
}
|
|
151399
|
-
}
|
|
151400
|
-
function directoryFallback(directory) {
|
|
151401
|
-
const canonical = path3.resolve(directory);
|
|
151402
|
-
const hash2 = Bun.hash(canonical).toString(16).slice(0, 12);
|
|
151403
|
-
return `dir:${hash2}`;
|
|
151404
|
-
}
|
|
151405
|
-
function resolveProjectIdentity(directory) {
|
|
151406
|
-
const resolved = path3.resolve(directory);
|
|
151407
|
-
const cached2 = resolvedCache.get(resolved);
|
|
151408
|
-
if (cached2 !== undefined) {
|
|
151409
|
-
return cached2;
|
|
151410
|
-
}
|
|
151411
|
-
const rootHash = getRootCommitHash(resolved);
|
|
151412
|
-
const identity = rootHash ? `git:${rootHash}` : directoryFallback(resolved);
|
|
151413
|
-
resolvedCache.set(resolved, identity);
|
|
151414
|
-
return identity;
|
|
151415
|
-
}
|
|
151416
|
-
var GIT_TIMEOUT_MS = 5000, resolvedCache;
|
|
151417
|
-
var init_project_identity = __esm(() => {
|
|
151418
|
-
resolvedCache = new Map;
|
|
151419
|
-
});
|
|
151420
|
-
|
|
151421
151762
|
// src/shared/rpc-notifications.ts
|
|
151422
151763
|
var exports_rpc_notifications = {};
|
|
151423
151764
|
__export(exports_rpc_notifications, {
|
|
@@ -152107,6 +152448,25 @@ var init_temporal_awareness = __esm(() => {
|
|
|
152107
152448
|
function clearInjectionCache(sessionId) {
|
|
152108
152449
|
injectionCache.delete(sessionId);
|
|
152109
152450
|
}
|
|
152451
|
+
function getVisibleMemoryIds(db, sessionId) {
|
|
152452
|
+
try {
|
|
152453
|
+
const row = db.prepare("SELECT memory_block_ids FROM session_meta WHERE session_id = ?").get(sessionId);
|
|
152454
|
+
if (!row?.memory_block_ids)
|
|
152455
|
+
return null;
|
|
152456
|
+
const parsed = JSON.parse(row.memory_block_ids);
|
|
152457
|
+
if (!Array.isArray(parsed))
|
|
152458
|
+
return null;
|
|
152459
|
+
const ids = new Set;
|
|
152460
|
+
for (const value of parsed) {
|
|
152461
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
152462
|
+
ids.add(value);
|
|
152463
|
+
}
|
|
152464
|
+
}
|
|
152465
|
+
return ids.size > 0 ? ids : null;
|
|
152466
|
+
} catch {
|
|
152467
|
+
return null;
|
|
152468
|
+
}
|
|
152469
|
+
}
|
|
152110
152470
|
function renderMemoryBlock(memories) {
|
|
152111
152471
|
const byCategory = new Map;
|
|
152112
152472
|
for (const m of memories) {
|
|
@@ -152202,8 +152562,9 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
|
|
|
152202
152562
|
}
|
|
152203
152563
|
memoryCount = memories.length;
|
|
152204
152564
|
memoryBlock = renderMemoryBlock(memories) ?? undefined;
|
|
152565
|
+
const renderedIds = memories.map((m) => m.id);
|
|
152205
152566
|
try {
|
|
152206
|
-
db.prepare("UPDATE session_meta SET memory_block_cache = ?, memory_block_count = ? WHERE session_id = ?").run(memoryBlock ?? "", memoryCount, sessionId);
|
|
152567
|
+
db.prepare("UPDATE session_meta SET memory_block_cache = ?, memory_block_count = ?, memory_block_ids = ? WHERE session_id = ?").run(memoryBlock ?? "", memoryCount, JSON.stringify(renderedIds), sessionId);
|
|
152207
152568
|
} catch (error48) {
|
|
152208
152569
|
const code = error48?.code;
|
|
152209
152570
|
if (code === "SQLITE_BUSY") {
|
|
@@ -153050,23 +153411,42 @@ function peekNoteNudgeText(db, sessionId, currentUserMessageId, projectIdentity)
|
|
|
153050
153411
|
return null;
|
|
153051
153412
|
}
|
|
153052
153413
|
const notes = getSessionNotes(db, sessionId);
|
|
153053
|
-
const
|
|
153054
|
-
const totalCount = notes.length +
|
|
153414
|
+
const readySmartNotes = projectIdentity ? getReadySmartNotes(db, projectIdentity) : [];
|
|
153415
|
+
const totalCount = notes.length + readySmartNotes.length;
|
|
153055
153416
|
if (totalCount === 0) {
|
|
153056
153417
|
sessionLog(sessionId, "note-nudge: triggerPending but no notes found, skipping");
|
|
153057
153418
|
clearPersistedNoteNudge(db, sessionId);
|
|
153058
153419
|
return null;
|
|
153059
153420
|
}
|
|
153421
|
+
const lastReadAt = getNoteLastReadAt(db, sessionId);
|
|
153422
|
+
if (lastReadAt > 0) {
|
|
153423
|
+
const mostRecentNoteActivity = maxNoteActivityTime([...notes, ...readySmartNotes]);
|
|
153424
|
+
if (mostRecentNoteActivity > 0 && lastReadAt > mostRecentNoteActivity) {
|
|
153425
|
+
sessionLog(sessionId, `note-nudge: suppressing \u2014 agent already ran ctx_note(read) at ${new Date(lastReadAt).toISOString()}, no new notes since ${new Date(mostRecentNoteActivity).toISOString()}`);
|
|
153426
|
+
clearPersistedNoteNudge(db, sessionId);
|
|
153427
|
+
return null;
|
|
153428
|
+
}
|
|
153429
|
+
}
|
|
153060
153430
|
const parts = [];
|
|
153061
153431
|
if (notes.length > 0) {
|
|
153062
153432
|
parts.push(`${notes.length} deferred note${notes.length === 1 ? "" : "s"}`);
|
|
153063
153433
|
}
|
|
153064
|
-
if (
|
|
153065
|
-
parts.push(`${
|
|
153434
|
+
if (readySmartNotes.length > 0) {
|
|
153435
|
+
parts.push(`${readySmartNotes.length} ready smart note${readySmartNotes.length === 1 ? "" : "s"}`);
|
|
153066
153436
|
}
|
|
153067
153437
|
sessionLog(sessionId, `note-nudge: delivering nudge for ${parts.join(" and ")}`);
|
|
153068
153438
|
return `You have ${parts.join(" and ")}. Review with ctx_note read \u2014 some may be actionable now.`;
|
|
153069
153439
|
}
|
|
153440
|
+
function maxNoteActivityTime(notes) {
|
|
153441
|
+
let max = 0;
|
|
153442
|
+
for (const note of notes) {
|
|
153443
|
+
if (note.updatedAt > max)
|
|
153444
|
+
max = note.updatedAt;
|
|
153445
|
+
if (note.readyAt !== null && note.readyAt > max)
|
|
153446
|
+
max = note.readyAt;
|
|
153447
|
+
}
|
|
153448
|
+
return max;
|
|
153449
|
+
}
|
|
153070
153450
|
function markNoteNudgeDelivered(db, sessionId, text, messageId) {
|
|
153071
153451
|
setPersistedDeliveredNoteNudge(db, sessionId, messageId ? text : "", messageId ?? "");
|
|
153072
153452
|
recordNoteNudgeDeliveryTime(sessionId);
|
|
@@ -153182,40 +153562,6 @@ var init_promotion = __esm(() => {
|
|
|
153182
153562
|
init_storage_memory();
|
|
153183
153563
|
init_storage_memory_embeddings();
|
|
153184
153564
|
});
|
|
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
153565
|
// src/features/magic-context/memory/index.ts
|
|
153220
153566
|
var init_memory = __esm(() => {
|
|
153221
153567
|
init_project_identity();
|
|
@@ -153507,12 +153853,23 @@ ${c.content}
|
|
|
153507
153853
|
const maxCompartmentsPerPass = deps.maxCompartmentsPerPass ?? DEFAULT_COMPRESSOR_MAX_COMPARTMENTS_PER_PASS;
|
|
153508
153854
|
const graceCompartments = deps.graceCompartments ?? DEFAULT_COMPRESSOR_GRACE_COMPARTMENTS;
|
|
153509
153855
|
const scored = scoreCompartments(db, sessionId, compartments);
|
|
153856
|
+
const depthHistogram = new Map;
|
|
153857
|
+
for (const s of scored) {
|
|
153858
|
+
const bucket = Math.round(s.averageDepth);
|
|
153859
|
+
depthHistogram.set(bucket, (depthHistogram.get(bucket) ?? 0) + 1);
|
|
153860
|
+
}
|
|
153861
|
+
const histText = [...depthHistogram.entries()].sort((a, b) => a[0] - b[0]).map(([d, n]) => `d${d}=${n}`).join(" ");
|
|
153862
|
+
const histKey = `${scored.length}|${histText}`;
|
|
153863
|
+
if (lastDepthHistogramBySession.get(sessionId) !== histKey) {
|
|
153864
|
+
lastDepthHistogramBySession.set(sessionId, histKey);
|
|
153865
|
+
sessionLog(sessionId, `compressor: depth histogram (${scored.length} total) ${histText}`);
|
|
153866
|
+
}
|
|
153510
153867
|
const floorHeadroom = compartments.length - floor;
|
|
153511
153868
|
if (floorHeadroom < 1) {
|
|
153512
153869
|
sessionLog(sessionId, `compressor: no floor headroom (${compartments.length} compartments, floor=${floor}), skipping`);
|
|
153513
153870
|
return false;
|
|
153514
153871
|
}
|
|
153515
|
-
const contiguous =
|
|
153872
|
+
const contiguous = selectCompressionBand(scored, {
|
|
153516
153873
|
maxPickable: maxCompartmentsPerPass,
|
|
153517
153874
|
maxMergeDepth,
|
|
153518
153875
|
graceCompartments,
|
|
@@ -153588,25 +153945,16 @@ ${c.content}
|
|
|
153588
153945
|
}
|
|
153589
153946
|
}
|
|
153590
153947
|
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
153948
|
return compartments.map((compartment, index) => {
|
|
153598
153949
|
const tokenEstimate = estimateTokens(`<compartment start="${compartment.startMessage}" end="${compartment.endMessage}" title="${compartment.title}">
|
|
153599
153950
|
${compartment.content}
|
|
153600
153951
|
</compartment>
|
|
153601
153952
|
`);
|
|
153602
153953
|
const averageDepth = getAverageCompressionDepth(db, sessionId, compartment.startMessage, compartment.endMessage);
|
|
153603
|
-
|
|
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 };
|
|
153954
|
+
return { compartment, index, tokenEstimate, averageDepth };
|
|
153607
153955
|
});
|
|
153608
153956
|
}
|
|
153609
|
-
function
|
|
153957
|
+
function selectCompressionBand(scored, constraints) {
|
|
153610
153958
|
const { maxPickable, maxMergeDepth, graceCompartments, floorHeadroom } = constraints;
|
|
153611
153959
|
const hardMaxPick = Math.max(0, Math.min(maxPickable, floorHeadroom));
|
|
153612
153960
|
if (hardMaxPick < 2)
|
|
@@ -153614,32 +153962,49 @@ function findOldestContiguousSameDepthBand(scored, constraints) {
|
|
|
153614
153962
|
const scanEnd = Math.max(0, scored.length - graceCompartments);
|
|
153615
153963
|
if (scanEnd < 2)
|
|
153616
153964
|
return [];
|
|
153617
|
-
|
|
153618
|
-
|
|
153619
|
-
const
|
|
153620
|
-
if (!
|
|
153621
|
-
i++;
|
|
153965
|
+
const tiers = new Set;
|
|
153966
|
+
for (let i = 0;i < scanEnd; i++) {
|
|
153967
|
+
const entry = scored[i];
|
|
153968
|
+
if (!entry)
|
|
153622
153969
|
continue;
|
|
153970
|
+
if (entry.averageDepth >= maxMergeDepth)
|
|
153971
|
+
continue;
|
|
153972
|
+
tiers.add(Math.round(entry.averageDepth));
|
|
153973
|
+
}
|
|
153974
|
+
if (tiers.size === 0)
|
|
153975
|
+
return [];
|
|
153976
|
+
const orderedTiers = [...tiers].sort((a, b) => a - b);
|
|
153977
|
+
for (const targetDepth of orderedTiers) {
|
|
153978
|
+
let i = 0;
|
|
153979
|
+
while (i < scanEnd) {
|
|
153980
|
+
const anchor = scored[i];
|
|
153981
|
+
if (!anchor) {
|
|
153982
|
+
i++;
|
|
153983
|
+
continue;
|
|
153984
|
+
}
|
|
153985
|
+
if (anchor.averageDepth >= maxMergeDepth || Math.round(anchor.averageDepth) !== targetDepth) {
|
|
153986
|
+
i++;
|
|
153987
|
+
continue;
|
|
153988
|
+
}
|
|
153989
|
+
let j = i;
|
|
153990
|
+
while (j < scanEnd) {
|
|
153991
|
+
const entry = scored[j];
|
|
153992
|
+
if (!entry)
|
|
153993
|
+
break;
|
|
153994
|
+
if (entry.averageDepth >= maxMergeDepth)
|
|
153995
|
+
break;
|
|
153996
|
+
if (Math.round(entry.averageDepth) !== targetDepth)
|
|
153997
|
+
break;
|
|
153998
|
+
if (j - i >= hardMaxPick)
|
|
153999
|
+
break;
|
|
154000
|
+
j++;
|
|
154001
|
+
}
|
|
154002
|
+
const runLen = j - i;
|
|
154003
|
+
if (runLen >= 2) {
|
|
154004
|
+
return scored.slice(i, j);
|
|
154005
|
+
}
|
|
154006
|
+
i = Math.max(j, i + 1);
|
|
153623
154007
|
}
|
|
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
154008
|
}
|
|
153644
154009
|
return [];
|
|
153645
154010
|
}
|
|
@@ -153823,7 +154188,7 @@ async function runCompressorPass(args) {
|
|
|
153823
154188
|
}
|
|
153824
154189
|
}
|
|
153825
154190
|
}
|
|
153826
|
-
var HISTORIAN_AGENT2 = "historian";
|
|
154191
|
+
var HISTORIAN_AGENT2 = "historian", lastDepthHistogramBySession;
|
|
153827
154192
|
var init_compartment_runner_compressor = __esm(() => {
|
|
153828
154193
|
init_magic_context();
|
|
153829
154194
|
init_storage();
|
|
@@ -153834,6 +154199,7 @@ var init_compartment_runner_compressor = __esm(() => {
|
|
|
153834
154199
|
init_compartment_parser();
|
|
153835
154200
|
init_compartment_prompt();
|
|
153836
154201
|
init_read_session_formatting();
|
|
154202
|
+
lastDepthHistogramBySession = new Map;
|
|
153837
154203
|
});
|
|
153838
154204
|
|
|
153839
154205
|
// src/hooks/magic-context/compartment-runner-drop-queue.ts
|
|
@@ -153970,6 +154336,7 @@ No new compartments or facts were written. Check the historian model/output and
|
|
|
153970
154336
|
appendCompartments(db, sessionId, newCompartments);
|
|
153971
154337
|
replaceSessionFacts(db, sessionId, validatedPass.facts ?? []);
|
|
153972
154338
|
clearHistorianFailureState(db, sessionId);
|
|
154339
|
+
clearEmergencyRecovery(db, sessionId);
|
|
153973
154340
|
})();
|
|
153974
154341
|
clearInjectionCache(sessionId);
|
|
153975
154342
|
if (deps.directory) {
|
|
@@ -162325,15 +162692,75 @@ function redactConfigValue(value) {
|
|
|
162325
162692
|
}
|
|
162326
162693
|
return typeof value;
|
|
162327
162694
|
}
|
|
162695
|
+
function migrateLegacyExperimental(rawConfig, warnings) {
|
|
162696
|
+
const experimental = rawConfig.experimental;
|
|
162697
|
+
if (typeof experimental !== "object" || experimental === null) {
|
|
162698
|
+
return rawConfig;
|
|
162699
|
+
}
|
|
162700
|
+
const exp = experimental;
|
|
162701
|
+
const hasUM = "user_memories" in exp;
|
|
162702
|
+
const hasPKF = "pin_key_files" in exp;
|
|
162703
|
+
if (!hasUM && !hasPKF) {
|
|
162704
|
+
return rawConfig;
|
|
162705
|
+
}
|
|
162706
|
+
const patched = { ...rawConfig };
|
|
162707
|
+
const dreamer = typeof patched.dreamer === "object" && patched.dreamer !== null ? { ...patched.dreamer } : {};
|
|
162708
|
+
const newExperimental = { ...exp };
|
|
162709
|
+
const coerceToObject = (value) => {
|
|
162710
|
+
if (typeof value === "boolean") {
|
|
162711
|
+
return { enabled: value };
|
|
162712
|
+
}
|
|
162713
|
+
if (typeof value === "object" && value !== null) {
|
|
162714
|
+
return { ...value };
|
|
162715
|
+
}
|
|
162716
|
+
return;
|
|
162717
|
+
};
|
|
162718
|
+
if (hasUM) {
|
|
162719
|
+
const oldUM = coerceToObject(exp.user_memories);
|
|
162720
|
+
if (oldUM !== undefined) {
|
|
162721
|
+
if (dreamer.user_memories === undefined) {
|
|
162722
|
+
dreamer.user_memories = oldUM;
|
|
162723
|
+
warnings.push('Migrated "experimental.user_memories" \u2192 "dreamer.user_memories" in-memory (run `doctor` to persist).');
|
|
162724
|
+
} else if (typeof dreamer.user_memories === "object" && dreamer.user_memories !== null) {
|
|
162725
|
+
dreamer.user_memories = {
|
|
162726
|
+
...oldUM,
|
|
162727
|
+
...dreamer.user_memories
|
|
162728
|
+
};
|
|
162729
|
+
}
|
|
162730
|
+
}
|
|
162731
|
+
delete newExperimental.user_memories;
|
|
162732
|
+
}
|
|
162733
|
+
if (hasPKF) {
|
|
162734
|
+
const oldPKF = coerceToObject(exp.pin_key_files);
|
|
162735
|
+
if (oldPKF !== undefined) {
|
|
162736
|
+
if (dreamer.pin_key_files === undefined) {
|
|
162737
|
+
dreamer.pin_key_files = oldPKF;
|
|
162738
|
+
warnings.push('Migrated "experimental.pin_key_files" \u2192 "dreamer.pin_key_files" in-memory (run `doctor` to persist).');
|
|
162739
|
+
} else if (typeof dreamer.pin_key_files === "object" && dreamer.pin_key_files !== null) {
|
|
162740
|
+
dreamer.pin_key_files = {
|
|
162741
|
+
...oldPKF,
|
|
162742
|
+
...dreamer.pin_key_files
|
|
162743
|
+
};
|
|
162744
|
+
}
|
|
162745
|
+
}
|
|
162746
|
+
delete newExperimental.pin_key_files;
|
|
162747
|
+
}
|
|
162748
|
+
patched.experimental = newExperimental;
|
|
162749
|
+
patched.dreamer = dreamer;
|
|
162750
|
+
return patched;
|
|
162751
|
+
}
|
|
162328
162752
|
function parsePluginConfig(rawConfig) {
|
|
162329
|
-
const
|
|
162753
|
+
const preMigrationWarnings = [];
|
|
162754
|
+
const migrated = migrateLegacyExperimental(rawConfig, preMigrationWarnings);
|
|
162755
|
+
const parsed = MagicContextConfigSchema.safeParse(migrated);
|
|
162330
162756
|
const disabledHooks = Array.isArray(rawConfig.disabled_hooks) ? rawConfig.disabled_hooks.filter((value) => typeof value === "string") : undefined;
|
|
162331
162757
|
const command = typeof rawConfig.command === "object" && rawConfig.command !== null ? rawConfig.command : undefined;
|
|
162332
162758
|
if (parsed.success) {
|
|
162333
162759
|
return {
|
|
162334
162760
|
...parsed.data,
|
|
162335
162761
|
disabled_hooks: disabledHooks,
|
|
162336
|
-
command
|
|
162762
|
+
command,
|
|
162763
|
+
...preMigrationWarnings.length > 0 ? { configWarnings: preMigrationWarnings } : {}
|
|
162337
162764
|
};
|
|
162338
162765
|
}
|
|
162339
162766
|
const defaults = MagicContextConfigSchema.parse({});
|
|
@@ -162357,17 +162784,23 @@ function parsePluginConfig(rawConfig) {
|
|
|
162357
162784
|
warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultVal)}.`);
|
|
162358
162785
|
}
|
|
162359
162786
|
}
|
|
162360
|
-
const
|
|
162787
|
+
const retryMigrated = migrateLegacyExperimental(patched, preMigrationWarnings);
|
|
162788
|
+
const retryParsed = MagicContextConfigSchema.safeParse(retryMigrated);
|
|
162361
162789
|
if (retryParsed.success) {
|
|
162362
162790
|
return {
|
|
162363
162791
|
...retryParsed.data,
|
|
162364
162792
|
disabled_hooks: disabledHooks,
|
|
162365
162793
|
command,
|
|
162366
|
-
configWarnings: warnings
|
|
162794
|
+
configWarnings: [...preMigrationWarnings, ...warnings]
|
|
162367
162795
|
};
|
|
162368
162796
|
}
|
|
162369
162797
|
warnings.push("Config recovery failed, using all defaults.");
|
|
162370
|
-
return {
|
|
162798
|
+
return {
|
|
162799
|
+
...defaults,
|
|
162800
|
+
disabled_hooks: disabledHooks,
|
|
162801
|
+
command,
|
|
162802
|
+
configWarnings: [...preMigrationWarnings, ...warnings]
|
|
162803
|
+
};
|
|
162371
162804
|
}
|
|
162372
162805
|
function loadPluginConfig(directory) {
|
|
162373
162806
|
const userDetected = detectConfigFile(getUserConfigBasePath());
|
|
@@ -164145,36 +164578,562 @@ function checkScheduleAndEnqueue(db, schedule) {
|
|
|
164145
164578
|
}
|
|
164146
164579
|
return enqueued;
|
|
164147
164580
|
}
|
|
164581
|
+
// src/features/magic-context/git-commits/git-log-reader.ts
|
|
164582
|
+
init_logger();
|
|
164583
|
+
import { execFile } from "child_process";
|
|
164584
|
+
import { promisify } from "util";
|
|
164585
|
+
var execFileAsync = promisify(execFile);
|
|
164586
|
+
var GIT_TIMEOUT_MS = 1e4;
|
|
164587
|
+
var DEFAULT_MAX_COMMITS = 5000;
|
|
164588
|
+
var RECORD_SEPARATOR = "\x1E";
|
|
164589
|
+
var FIELD_SEPARATOR = "\x1F";
|
|
164590
|
+
async function readGitCommits(directory, options = {}) {
|
|
164591
|
+
const args = [
|
|
164592
|
+
"log",
|
|
164593
|
+
options.branch ?? "HEAD",
|
|
164594
|
+
"--no-merges",
|
|
164595
|
+
`--max-count=${options.maxCommits ?? DEFAULT_MAX_COMMITS}`,
|
|
164596
|
+
`--format=%H${FIELD_SEPARATOR}%s${FIELD_SEPARATOR}%ae${FIELD_SEPARATOR}%ct${FIELD_SEPARATOR}%b${RECORD_SEPARATOR}`
|
|
164597
|
+
];
|
|
164598
|
+
if (options.sinceMs !== undefined && options.sinceMs > 0) {
|
|
164599
|
+
const iso = new Date(options.sinceMs).toISOString();
|
|
164600
|
+
args.push(`--since=${iso}`);
|
|
164601
|
+
}
|
|
164602
|
+
let stdout;
|
|
164603
|
+
try {
|
|
164604
|
+
const result = await execFileAsync("git", args, {
|
|
164605
|
+
cwd: directory,
|
|
164606
|
+
timeout: GIT_TIMEOUT_MS,
|
|
164607
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
164608
|
+
encoding: "utf8"
|
|
164609
|
+
});
|
|
164610
|
+
stdout = result.stdout;
|
|
164611
|
+
} catch (error48) {
|
|
164612
|
+
const message = error48 instanceof Error ? error48.message : String(error48);
|
|
164613
|
+
log(`[git-commits] readGitCommits failed at cwd=${directory}: ${message.slice(0, 500)}`);
|
|
164614
|
+
return [];
|
|
164615
|
+
}
|
|
164616
|
+
if (stdout.trim().length === 0) {
|
|
164617
|
+
log(`[git-commits] readGitCommits returned empty stdout at cwd=${directory} (sinceMs=${options.sinceMs ?? "none"} args=${args.slice(0, 4).join(" ")})`);
|
|
164618
|
+
}
|
|
164619
|
+
return parseGitLogOutput(stdout);
|
|
164620
|
+
}
|
|
164621
|
+
function parseGitLogOutput(stdout) {
|
|
164622
|
+
const commits = [];
|
|
164623
|
+
const records = stdout.split(RECORD_SEPARATOR);
|
|
164624
|
+
for (const rawRecord of records) {
|
|
164625
|
+
const record2 = rawRecord.replace(/^\s+/, "");
|
|
164626
|
+
if (!record2)
|
|
164627
|
+
continue;
|
|
164628
|
+
const fields = [];
|
|
164629
|
+
let remaining = record2;
|
|
164630
|
+
for (let i = 0;i < 4; i++) {
|
|
164631
|
+
const idx = remaining.indexOf(FIELD_SEPARATOR);
|
|
164632
|
+
if (idx < 0)
|
|
164633
|
+
break;
|
|
164634
|
+
fields.push(remaining.slice(0, idx));
|
|
164635
|
+
remaining = remaining.slice(idx + FIELD_SEPARATOR.length);
|
|
164636
|
+
}
|
|
164637
|
+
fields.push(remaining);
|
|
164638
|
+
if (fields.length < 5)
|
|
164639
|
+
continue;
|
|
164640
|
+
const sha = fields[0].trim();
|
|
164641
|
+
const subject = fields[1].trim();
|
|
164642
|
+
const author = fields[2].trim();
|
|
164643
|
+
const timeSec = Number.parseInt(fields[3].trim(), 10);
|
|
164644
|
+
const body = fields[4].trim();
|
|
164645
|
+
if (sha.length !== 40 || !Number.isFinite(timeSec) || timeSec <= 0) {
|
|
164646
|
+
continue;
|
|
164647
|
+
}
|
|
164648
|
+
const message = body.length > 0 ? `${subject}
|
|
164649
|
+
|
|
164650
|
+
${body}` : subject;
|
|
164651
|
+
commits.push({
|
|
164652
|
+
sha,
|
|
164653
|
+
shortSha: sha.slice(0, 7),
|
|
164654
|
+
message,
|
|
164655
|
+
author: author.length > 0 ? author : null,
|
|
164656
|
+
committedAtMs: timeSec * 1000
|
|
164657
|
+
});
|
|
164658
|
+
}
|
|
164659
|
+
return commits;
|
|
164660
|
+
}
|
|
164661
|
+
// src/features/magic-context/git-commits/indexer.ts
|
|
164662
|
+
init_logger();
|
|
164663
|
+
init_embedding();
|
|
164664
|
+
|
|
164665
|
+
// src/features/magic-context/git-commits/storage-git-commit-embeddings.ts
|
|
164666
|
+
var saveStatements = new WeakMap;
|
|
164667
|
+
var loadProjectStatements = new WeakMap;
|
|
164668
|
+
var loadUnembeddedStatements = new WeakMap;
|
|
164669
|
+
var countEmbeddedStatements = new WeakMap;
|
|
164670
|
+
var clearProjectStatements = new WeakMap;
|
|
164671
|
+
function getSaveStatement(db) {
|
|
164672
|
+
let stmt = saveStatements.get(db);
|
|
164673
|
+
if (!stmt) {
|
|
164674
|
+
stmt = db.prepare(`INSERT INTO git_commit_embeddings (sha, embedding, model_id, created_at)
|
|
164675
|
+
VALUES (?, ?, ?, ?)
|
|
164676
|
+
ON CONFLICT(sha) DO UPDATE SET
|
|
164677
|
+
embedding = excluded.embedding,
|
|
164678
|
+
model_id = excluded.model_id,
|
|
164679
|
+
created_at = excluded.created_at`);
|
|
164680
|
+
saveStatements.set(db, stmt);
|
|
164681
|
+
}
|
|
164682
|
+
return stmt;
|
|
164683
|
+
}
|
|
164684
|
+
function getLoadProjectStatement(db) {
|
|
164685
|
+
let stmt = loadProjectStatements.get(db);
|
|
164686
|
+
if (!stmt) {
|
|
164687
|
+
stmt = db.prepare(`SELECT e.sha AS sha, e.embedding AS embedding, e.model_id AS model_id
|
|
164688
|
+
FROM git_commit_embeddings e
|
|
164689
|
+
JOIN git_commits c ON c.sha = e.sha
|
|
164690
|
+
WHERE c.project_path = ?`);
|
|
164691
|
+
loadProjectStatements.set(db, stmt);
|
|
164692
|
+
}
|
|
164693
|
+
return stmt;
|
|
164694
|
+
}
|
|
164695
|
+
function getLoadUnembeddedStatement(db) {
|
|
164696
|
+
let stmt = loadUnembeddedStatements.get(db);
|
|
164697
|
+
if (!stmt) {
|
|
164698
|
+
stmt = db.prepare(`SELECT c.sha AS sha, c.message AS message
|
|
164699
|
+
FROM git_commits c
|
|
164700
|
+
LEFT JOIN git_commit_embeddings e ON c.sha = e.sha
|
|
164701
|
+
WHERE c.project_path = ? AND e.sha IS NULL
|
|
164702
|
+
ORDER BY c.committed_at DESC
|
|
164703
|
+
LIMIT ?`);
|
|
164704
|
+
loadUnembeddedStatements.set(db, stmt);
|
|
164705
|
+
}
|
|
164706
|
+
return stmt;
|
|
164707
|
+
}
|
|
164708
|
+
function getCountEmbeddedStatement(db) {
|
|
164709
|
+
let stmt = countEmbeddedStatements.get(db);
|
|
164710
|
+
if (!stmt) {
|
|
164711
|
+
stmt = db.prepare(`SELECT COUNT(*) AS count FROM git_commit_embeddings e
|
|
164712
|
+
JOIN git_commits c ON c.sha = e.sha WHERE c.project_path = ?`);
|
|
164713
|
+
countEmbeddedStatements.set(db, stmt);
|
|
164714
|
+
}
|
|
164715
|
+
return stmt;
|
|
164716
|
+
}
|
|
164717
|
+
function saveCommitEmbedding(db, sha, embedding, modelId) {
|
|
164718
|
+
const bytes = new Uint8Array(embedding.buffer, embedding.byteOffset, embedding.byteLength);
|
|
164719
|
+
getSaveStatement(db).run(sha, bytes, modelId, Date.now());
|
|
164720
|
+
}
|
|
164721
|
+
function loadProjectCommitEmbeddings(db, projectPath) {
|
|
164722
|
+
const rows = getLoadProjectStatement(db).all(projectPath);
|
|
164723
|
+
const map2 = new Map;
|
|
164724
|
+
for (const row of rows) {
|
|
164725
|
+
const buffer2 = row.embedding.buffer.slice(row.embedding.byteOffset, row.embedding.byteOffset + row.embedding.byteLength);
|
|
164726
|
+
map2.set(row.sha, new Float32Array(buffer2));
|
|
164727
|
+
}
|
|
164728
|
+
return map2;
|
|
164729
|
+
}
|
|
164730
|
+
function loadUnembeddedCommits(db, projectPath, limit) {
|
|
164731
|
+
return getLoadUnembeddedStatement(db).all(projectPath, limit);
|
|
164732
|
+
}
|
|
164733
|
+
function countEmbeddedCommits(db, projectPath) {
|
|
164734
|
+
const row = getCountEmbeddedStatement(db).get(projectPath);
|
|
164735
|
+
return row?.count ?? 0;
|
|
164736
|
+
}
|
|
164737
|
+
|
|
164738
|
+
// src/features/magic-context/git-commits/storage-git-commits.ts
|
|
164739
|
+
init_logger();
|
|
164740
|
+
var insertStatements = new WeakMap;
|
|
164741
|
+
var existingShasStatements = new WeakMap;
|
|
164742
|
+
var projectCountStatements = new WeakMap;
|
|
164743
|
+
var evictStatements = new WeakMap;
|
|
164744
|
+
var latestCommitTimeStatements = new WeakMap;
|
|
164745
|
+
function getInsertStatement(db) {
|
|
164746
|
+
let stmt = insertStatements.get(db);
|
|
164747
|
+
if (!stmt) {
|
|
164748
|
+
stmt = db.prepare(`INSERT INTO git_commits (sha, project_path, short_sha, message, author, committed_at, indexed_at)
|
|
164749
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
164750
|
+
ON CONFLICT(sha) DO UPDATE SET
|
|
164751
|
+
project_path = excluded.project_path,
|
|
164752
|
+
short_sha = excluded.short_sha,
|
|
164753
|
+
message = excluded.message,
|
|
164754
|
+
author = excluded.author,
|
|
164755
|
+
committed_at = excluded.committed_at,
|
|
164756
|
+
indexed_at = excluded.indexed_at
|
|
164757
|
+
WHERE git_commits.message != excluded.message`);
|
|
164758
|
+
insertStatements.set(db, stmt);
|
|
164759
|
+
}
|
|
164760
|
+
return stmt;
|
|
164761
|
+
}
|
|
164762
|
+
function getExistingShasStatement(db) {
|
|
164763
|
+
let stmt = existingShasStatements.get(db);
|
|
164764
|
+
if (!stmt) {
|
|
164765
|
+
stmt = db.prepare("SELECT sha FROM git_commits WHERE project_path = ?");
|
|
164766
|
+
existingShasStatements.set(db, stmt);
|
|
164767
|
+
}
|
|
164768
|
+
return stmt;
|
|
164769
|
+
}
|
|
164770
|
+
function getProjectCountStatement(db) {
|
|
164771
|
+
let stmt = projectCountStatements.get(db);
|
|
164772
|
+
if (!stmt) {
|
|
164773
|
+
stmt = db.prepare("SELECT COUNT(*) AS count FROM git_commits WHERE project_path = ?");
|
|
164774
|
+
projectCountStatements.set(db, stmt);
|
|
164775
|
+
}
|
|
164776
|
+
return stmt;
|
|
164777
|
+
}
|
|
164778
|
+
function getLatestCommitTimeStatement(db) {
|
|
164779
|
+
let stmt = latestCommitTimeStatements.get(db);
|
|
164780
|
+
if (!stmt) {
|
|
164781
|
+
stmt = db.prepare("SELECT MAX(committed_at) AS latest FROM git_commits WHERE project_path = ?");
|
|
164782
|
+
latestCommitTimeStatements.set(db, stmt);
|
|
164783
|
+
}
|
|
164784
|
+
return stmt;
|
|
164785
|
+
}
|
|
164786
|
+
function getEvictStatement(db) {
|
|
164787
|
+
let stmt = evictStatements.get(db);
|
|
164788
|
+
if (!stmt) {
|
|
164789
|
+
stmt = db.prepare(`DELETE FROM git_commits
|
|
164790
|
+
WHERE sha IN (
|
|
164791
|
+
SELECT sha FROM git_commits
|
|
164792
|
+
WHERE project_path = ?
|
|
164793
|
+
ORDER BY committed_at ASC
|
|
164794
|
+
LIMIT ?
|
|
164795
|
+
)`);
|
|
164796
|
+
evictStatements.set(db, stmt);
|
|
164797
|
+
}
|
|
164798
|
+
return stmt;
|
|
164799
|
+
}
|
|
164800
|
+
function upsertCommits(db, projectPath, commits) {
|
|
164801
|
+
if (commits.length === 0)
|
|
164802
|
+
return { inserted: 0, updated: 0 };
|
|
164803
|
+
const existing = new Set;
|
|
164804
|
+
for (const row of getExistingShasStatement(db).all(projectPath)) {
|
|
164805
|
+
existing.add(row.sha);
|
|
164806
|
+
}
|
|
164807
|
+
let inserted = 0;
|
|
164808
|
+
let updated = 0;
|
|
164809
|
+
const now = Date.now();
|
|
164810
|
+
const insertStmt = getInsertStatement(db);
|
|
164811
|
+
db.transaction(() => {
|
|
164812
|
+
for (const commit of commits) {
|
|
164813
|
+
const result = insertStmt.run(commit.sha, projectPath, commit.shortSha, commit.message, commit.author, commit.committedAtMs, now);
|
|
164814
|
+
if (result.changes > 0) {
|
|
164815
|
+
if (existing.has(commit.sha)) {
|
|
164816
|
+
updated++;
|
|
164817
|
+
} else {
|
|
164818
|
+
inserted++;
|
|
164819
|
+
existing.add(commit.sha);
|
|
164820
|
+
}
|
|
164821
|
+
}
|
|
164822
|
+
}
|
|
164823
|
+
})();
|
|
164824
|
+
return { inserted, updated };
|
|
164825
|
+
}
|
|
164826
|
+
function getCommitCount(db, projectPath) {
|
|
164827
|
+
const row = getProjectCountStatement(db).get(projectPath);
|
|
164828
|
+
return row?.count ?? 0;
|
|
164829
|
+
}
|
|
164830
|
+
function getLatestIndexedCommitTimeMs(db, projectPath) {
|
|
164831
|
+
const row = getLatestCommitTimeStatement(db).get(projectPath);
|
|
164832
|
+
return row?.latest ?? null;
|
|
164833
|
+
}
|
|
164834
|
+
function evictOldestCommits(db, projectPath, excess) {
|
|
164835
|
+
if (excess <= 0)
|
|
164836
|
+
return 0;
|
|
164837
|
+
const before = getCommitCount(db, projectPath);
|
|
164838
|
+
getEvictStatement(db).run(projectPath, excess);
|
|
164839
|
+
const after = getCommitCount(db, projectPath);
|
|
164840
|
+
return Math.max(0, before - after);
|
|
164841
|
+
}
|
|
164842
|
+
function enforceProjectCap(db, projectPath, maxCommits) {
|
|
164843
|
+
if (maxCommits <= 0)
|
|
164844
|
+
return 0;
|
|
164845
|
+
const count = getCommitCount(db, projectPath);
|
|
164846
|
+
if (count <= maxCommits)
|
|
164847
|
+
return 0;
|
|
164848
|
+
const excess = count - maxCommits;
|
|
164849
|
+
const evicted = evictOldestCommits(db, projectPath, excess);
|
|
164850
|
+
if (evicted > 0) {
|
|
164851
|
+
log(`[git-commits] evicted ${evicted} oldest commits for project ${projectPath} (cap=${maxCommits}, was=${count})`);
|
|
164852
|
+
}
|
|
164853
|
+
return evicted;
|
|
164854
|
+
}
|
|
164855
|
+
|
|
164856
|
+
// src/features/magic-context/git-commits/indexer.ts
|
|
164857
|
+
var MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
164858
|
+
var EMBED_BATCH_SIZE = 16;
|
|
164859
|
+
var EMBED_MAX_PER_SWEEP = 500;
|
|
164860
|
+
var EMBED_SWEEP_MAX_WALL_CLOCK_MS = 5 * 60 * 1000;
|
|
164861
|
+
var indexInProgress = new Set;
|
|
164862
|
+
var embedInProgress = new Set;
|
|
164863
|
+
async function indexCommitsForProject(db, projectPath, directory, embeddingConfig2, options) {
|
|
164864
|
+
const result = {
|
|
164865
|
+
scanned: 0,
|
|
164866
|
+
inserted: 0,
|
|
164867
|
+
updated: 0,
|
|
164868
|
+
evicted: 0,
|
|
164869
|
+
embedded: 0
|
|
164870
|
+
};
|
|
164871
|
+
if (indexInProgress.has(projectPath)) {
|
|
164872
|
+
log(`[git-commits] index already in progress for ${projectPath}, skipping`);
|
|
164873
|
+
return result;
|
|
164874
|
+
}
|
|
164875
|
+
indexInProgress.add(projectPath);
|
|
164876
|
+
try {
|
|
164877
|
+
const latestIndexed = getLatestIndexedCommitTimeMs(db, projectPath);
|
|
164878
|
+
const sinceMs = latestIndexed !== null ? Math.max(latestIndexed - 60000, Date.now() - options.sinceDays * MS_PER_DAY) : Date.now() - options.sinceDays * MS_PER_DAY;
|
|
164879
|
+
const commits = await readGitCommits(directory, {
|
|
164880
|
+
sinceMs,
|
|
164881
|
+
maxCommits: options.maxCommits
|
|
164882
|
+
});
|
|
164883
|
+
result.scanned = commits.length;
|
|
164884
|
+
if (commits.length === 0) {
|
|
164885
|
+
result.evicted = enforceProjectCap(db, projectPath, options.maxCommits);
|
|
164886
|
+
log(`[git-commits] no new commits for ${projectPath} (sinceMs=${sinceMs} latestIndexed=${latestIndexed ?? "none"} evicted=${result.evicted})`);
|
|
164887
|
+
return result;
|
|
164888
|
+
}
|
|
164889
|
+
log(`[git-commits] read ${commits.length} commits for ${projectPath} (sinceMs=${sinceMs} latestIndexed=${latestIndexed ?? "none"})`);
|
|
164890
|
+
const upsert = upsertCommits(db, projectPath, commits);
|
|
164891
|
+
result.inserted = upsert.inserted;
|
|
164892
|
+
result.updated = upsert.updated;
|
|
164893
|
+
result.evicted = enforceProjectCap(db, projectPath, options.maxCommits);
|
|
164894
|
+
if (options.skipEmbed || !isEmbeddingEnabled()) {
|
|
164895
|
+
log(`[git-commits] indexed ${projectPath}: scanned=${result.scanned} inserted=${result.inserted} updated=${result.updated} evicted=${result.evicted} embedded=0 (embedding skipped: skipEmbed=${options.skipEmbed === true} embeddingEnabled=${isEmbeddingEnabled()})`);
|
|
164896
|
+
return result;
|
|
164897
|
+
}
|
|
164898
|
+
result.embedded = await embedUnembeddedCommits(db, projectPath, embeddingConfig2);
|
|
164899
|
+
log(`[git-commits] indexed ${projectPath}: scanned=${result.scanned} inserted=${result.inserted} updated=${result.updated} evicted=${result.evicted} embedded=${result.embedded}`);
|
|
164900
|
+
return result;
|
|
164901
|
+
} finally {
|
|
164902
|
+
indexInProgress.delete(projectPath);
|
|
164903
|
+
}
|
|
164904
|
+
}
|
|
164905
|
+
async function embedUnembeddedCommits(db, projectPath, _config) {
|
|
164906
|
+
if (embedInProgress.has(projectPath)) {
|
|
164907
|
+
return 0;
|
|
164908
|
+
}
|
|
164909
|
+
if (!isEmbeddingEnabled()) {
|
|
164910
|
+
return 0;
|
|
164911
|
+
}
|
|
164912
|
+
embedInProgress.add(projectPath);
|
|
164913
|
+
const startedAt = Date.now();
|
|
164914
|
+
const deadline = startedAt + EMBED_SWEEP_MAX_WALL_CLOCK_MS;
|
|
164915
|
+
let total = 0;
|
|
164916
|
+
try {
|
|
164917
|
+
while (Date.now() < deadline && total < EMBED_MAX_PER_SWEEP) {
|
|
164918
|
+
const rows = loadUnembeddedCommits(db, projectPath, EMBED_BATCH_SIZE);
|
|
164919
|
+
if (rows.length === 0)
|
|
164920
|
+
break;
|
|
164921
|
+
let embeddedThisBatch = 0;
|
|
164922
|
+
try {
|
|
164923
|
+
const embeddings = await embedBatch(rows.map((row) => row.message));
|
|
164924
|
+
const modelId = getEmbeddingModelId();
|
|
164925
|
+
if (modelId === "off")
|
|
164926
|
+
break;
|
|
164927
|
+
db.transaction(() => {
|
|
164928
|
+
for (const [index, row] of rows.entries()) {
|
|
164929
|
+
const embedding = embeddings[index];
|
|
164930
|
+
if (!embedding)
|
|
164931
|
+
continue;
|
|
164932
|
+
saveCommitEmbedding(db, row.sha, embedding, modelId);
|
|
164933
|
+
embeddedThisBatch += 1;
|
|
164934
|
+
}
|
|
164935
|
+
})();
|
|
164936
|
+
} catch (error48) {
|
|
164937
|
+
log(`[git-commits] embed batch failed for ${projectPath}: ${error48 instanceof Error ? error48.message : String(error48)}`);
|
|
164938
|
+
break;
|
|
164939
|
+
}
|
|
164940
|
+
if (embeddedThisBatch === 0)
|
|
164941
|
+
break;
|
|
164942
|
+
total += embeddedThisBatch;
|
|
164943
|
+
if (embeddedThisBatch < rows.length)
|
|
164944
|
+
break;
|
|
164945
|
+
}
|
|
164946
|
+
if (total > 0) {
|
|
164947
|
+
const totalEmbedded = countEmbeddedCommits(db, projectPath);
|
|
164948
|
+
log(`[git-commits] embedded ${total} commits for ${projectPath} (total embedded: ${totalEmbedded})`);
|
|
164949
|
+
}
|
|
164950
|
+
return total;
|
|
164951
|
+
} finally {
|
|
164952
|
+
embedInProgress.delete(projectPath);
|
|
164953
|
+
}
|
|
164954
|
+
}
|
|
164955
|
+
// src/features/magic-context/git-commits/search-git-commits.ts
|
|
164956
|
+
init_logger();
|
|
164957
|
+
init_storage_memory_fts();
|
|
164958
|
+
var ftsStatements = new WeakMap;
|
|
164959
|
+
var ftsPlainStatements = new WeakMap;
|
|
164960
|
+
var getBySHAStatements = new WeakMap;
|
|
164961
|
+
function rowToCommit(row) {
|
|
164962
|
+
return {
|
|
164963
|
+
sha: row.sha,
|
|
164964
|
+
shortSha: row.short_sha,
|
|
164965
|
+
projectPath: row.project_path,
|
|
164966
|
+
message: row.message,
|
|
164967
|
+
author: row.author,
|
|
164968
|
+
committedAtMs: row.committed_at,
|
|
164969
|
+
indexedAtMs: row.indexed_at
|
|
164970
|
+
};
|
|
164971
|
+
}
|
|
164972
|
+
function getFtsStatement(db) {
|
|
164973
|
+
let stmt = ftsStatements.get(db);
|
|
164974
|
+
if (!stmt) {
|
|
164975
|
+
stmt = db.prepare(`SELECT c.sha AS sha, c.project_path AS project_path, c.short_sha AS short_sha,
|
|
164976
|
+
c.message AS message, c.author AS author,
|
|
164977
|
+
c.committed_at AS committed_at, c.indexed_at AS indexed_at
|
|
164978
|
+
FROM git_commits_fts
|
|
164979
|
+
INNER JOIN git_commits c ON c.sha = git_commits_fts.sha
|
|
164980
|
+
WHERE c.project_path = ? AND git_commits_fts MATCH ?
|
|
164981
|
+
ORDER BY bm25(git_commits_fts) LIMIT ?`);
|
|
164982
|
+
ftsStatements.set(db, stmt);
|
|
164983
|
+
}
|
|
164984
|
+
return stmt;
|
|
164985
|
+
}
|
|
164986
|
+
function getLikeFallbackStatement(db) {
|
|
164987
|
+
let stmt = ftsPlainStatements.get(db);
|
|
164988
|
+
if (!stmt) {
|
|
164989
|
+
stmt = db.prepare(`SELECT sha, project_path, short_sha, message, author, committed_at, indexed_at
|
|
164990
|
+
FROM git_commits
|
|
164991
|
+
WHERE project_path = ? AND lower(message) LIKE '%' || lower(?) || '%'
|
|
164992
|
+
ORDER BY committed_at DESC LIMIT ?`);
|
|
164993
|
+
ftsPlainStatements.set(db, stmt);
|
|
164994
|
+
}
|
|
164995
|
+
return stmt;
|
|
164996
|
+
}
|
|
164997
|
+
function getBySHAStatement(db) {
|
|
164998
|
+
let stmt = getBySHAStatements.get(db);
|
|
164999
|
+
if (!stmt) {
|
|
165000
|
+
stmt = db.prepare(`SELECT sha, project_path, short_sha, message, author, committed_at, indexed_at
|
|
165001
|
+
FROM git_commits WHERE sha = ?`);
|
|
165002
|
+
getBySHAStatements.set(db, stmt);
|
|
165003
|
+
}
|
|
165004
|
+
return stmt;
|
|
165005
|
+
}
|
|
165006
|
+
function clamp01(value) {
|
|
165007
|
+
if (!Number.isFinite(value))
|
|
165008
|
+
return 0;
|
|
165009
|
+
return Math.min(1, Math.max(0, value));
|
|
165010
|
+
}
|
|
165011
|
+
function searchGitCommitsSync(db, projectPath, query, options) {
|
|
165012
|
+
const trimmed = query.trim();
|
|
165013
|
+
if (trimmed.length === 0 || options.limit <= 0)
|
|
165014
|
+
return [];
|
|
165015
|
+
const semanticWeight = options.semanticWeight ?? 0.7;
|
|
165016
|
+
const ftsWeight = options.ftsWeight ?? 0.3;
|
|
165017
|
+
const singleSourcePenalty = options.singleSourcePenalty ?? 0.8;
|
|
165018
|
+
const fetchLimit = Math.max(options.limit * 3, 30);
|
|
165019
|
+
const ftsCandidates = [];
|
|
165020
|
+
const sanitized = sanitizeFtsQuery(trimmed);
|
|
165021
|
+
if (sanitized.length > 0) {
|
|
165022
|
+
try {
|
|
165023
|
+
for (const row of getFtsStatement(db).all(projectPath, sanitized, fetchLimit)) {
|
|
165024
|
+
ftsCandidates.push(rowToCommit(row));
|
|
165025
|
+
}
|
|
165026
|
+
} catch (error48) {
|
|
165027
|
+
log(`[git-commits] FTS query failed for "${trimmed}": ${error48 instanceof Error ? error48.message : String(error48)}`);
|
|
165028
|
+
}
|
|
165029
|
+
}
|
|
165030
|
+
if (ftsCandidates.length === 0) {
|
|
165031
|
+
for (const row of getLikeFallbackStatement(db).all(projectPath, trimmed, fetchLimit)) {
|
|
165032
|
+
ftsCandidates.push(rowToCommit(row));
|
|
165033
|
+
}
|
|
165034
|
+
}
|
|
165035
|
+
const ftsScores = new Map;
|
|
165036
|
+
ftsCandidates.forEach((commit, rank) => {
|
|
165037
|
+
ftsScores.set(commit.sha, 1 / (rank + 1));
|
|
165038
|
+
});
|
|
165039
|
+
const semanticScores = new Map;
|
|
165040
|
+
if (options.queryEmbedding) {
|
|
165041
|
+
const embeddings = loadProjectCommitEmbeddings(db, projectPath);
|
|
165042
|
+
for (const [sha, embedding] of embeddings.entries()) {
|
|
165043
|
+
const similarity = clamp01(cosineSimilarity(options.queryEmbedding, embedding));
|
|
165044
|
+
if (similarity > 0) {
|
|
165045
|
+
semanticScores.set(sha, similarity);
|
|
165046
|
+
}
|
|
165047
|
+
}
|
|
165048
|
+
}
|
|
165049
|
+
const bySha = new Map;
|
|
165050
|
+
for (const commit of ftsCandidates)
|
|
165051
|
+
bySha.set(commit.sha, commit);
|
|
165052
|
+
const getCommitStmt = getBySHAStatement(db);
|
|
165053
|
+
for (const sha of semanticScores.keys()) {
|
|
165054
|
+
if (bySha.has(sha))
|
|
165055
|
+
continue;
|
|
165056
|
+
const row = getCommitStmt.get(sha);
|
|
165057
|
+
if (row && row.project_path === projectPath) {
|
|
165058
|
+
bySha.set(sha, rowToCommit(row));
|
|
165059
|
+
}
|
|
165060
|
+
}
|
|
165061
|
+
const results = [];
|
|
165062
|
+
for (const [sha, commit] of bySha.entries()) {
|
|
165063
|
+
const sem = semanticScores.get(sha);
|
|
165064
|
+
const fts = ftsScores.get(sha);
|
|
165065
|
+
let score = 0;
|
|
165066
|
+
let matchType = "fts";
|
|
165067
|
+
if (sem !== undefined && fts !== undefined) {
|
|
165068
|
+
score = semanticWeight * sem + ftsWeight * fts;
|
|
165069
|
+
matchType = "hybrid";
|
|
165070
|
+
} else if (sem !== undefined) {
|
|
165071
|
+
score = sem * singleSourcePenalty;
|
|
165072
|
+
matchType = "semantic";
|
|
165073
|
+
} else if (fts !== undefined) {
|
|
165074
|
+
score = fts * singleSourcePenalty;
|
|
165075
|
+
matchType = "fts";
|
|
165076
|
+
}
|
|
165077
|
+
if (score <= 0)
|
|
165078
|
+
continue;
|
|
165079
|
+
results.push({ commit, score, matchType });
|
|
165080
|
+
}
|
|
165081
|
+
results.sort((left, right) => {
|
|
165082
|
+
if (right.score !== left.score)
|
|
165083
|
+
return right.score - left.score;
|
|
165084
|
+
return right.commit.committedAtMs - left.commit.committedAtMs;
|
|
165085
|
+
});
|
|
165086
|
+
return results.slice(0, options.limit);
|
|
165087
|
+
}
|
|
164148
165088
|
// src/plugin/dream-timer.ts
|
|
164149
165089
|
init_embedding();
|
|
165090
|
+
init_project_identity();
|
|
164150
165091
|
init_storage();
|
|
164151
165092
|
init_logger();
|
|
164152
165093
|
var DREAM_TIMER_INTERVAL_MS = 15 * 60 * 1000;
|
|
164153
165094
|
var activeTimer = null;
|
|
164154
|
-
var
|
|
165095
|
+
var registeredProjects = new Map;
|
|
164155
165096
|
function startDreamScheduleTimer(args) {
|
|
164156
|
-
|
|
164157
|
-
|
|
164158
|
-
|
|
164159
|
-
|
|
164160
|
-
const {
|
|
164161
|
-
client,
|
|
164162
|
-
dreamerConfig,
|
|
164163
|
-
embeddingConfig: embeddingConfig2,
|
|
164164
|
-
memoryEnabled,
|
|
164165
|
-
experimentalUserMemories,
|
|
164166
|
-
experimentalPinKeyFiles
|
|
164167
|
-
} = args;
|
|
164168
|
-
const dreamingEnabled = Boolean(dreamerConfig?.enabled && dreamerConfig.schedule?.trim());
|
|
164169
|
-
const embeddingSweepEnabled = memoryEnabled && embeddingConfig2.provider !== "off";
|
|
164170
|
-
if (!dreamingEnabled && !embeddingSweepEnabled) {
|
|
165097
|
+
const dreamingEnabled = Boolean(args.dreamerConfig?.enabled && args.dreamerConfig.schedule?.trim());
|
|
165098
|
+
const embeddingSweepEnabled = args.memoryEnabled && args.embeddingConfig.provider !== "off";
|
|
165099
|
+
const commitIndexingEnabled = args.gitCommitIndexing?.enabled === true;
|
|
165100
|
+
if (!dreamingEnabled && !embeddingSweepEnabled && !commitIndexingEnabled) {
|
|
164171
165101
|
return;
|
|
164172
165102
|
}
|
|
164173
|
-
const
|
|
164174
|
-
|
|
164175
|
-
|
|
164176
|
-
|
|
164177
|
-
|
|
165103
|
+
const isNewRegistration = !registeredProjects.has(args.directory);
|
|
165104
|
+
registeredProjects.set(args.directory, args);
|
|
165105
|
+
if (isNewRegistration) {
|
|
165106
|
+
log(`[dreamer] registered project ${args.directory} (dreaming=${dreamingEnabled} embeddings=${embeddingSweepEnabled} commits=${commitIndexingEnabled}; total=${registeredProjects.size})`);
|
|
165107
|
+
}
|
|
165108
|
+
if (!activeTimer) {
|
|
165109
|
+
log(`[dreamer] started independent schedule timer (every ${DREAM_TIMER_INTERVAL_MS / 60000}m)`);
|
|
165110
|
+
runTick("startup");
|
|
165111
|
+
const timer = setInterval(() => runTick("interval"), DREAM_TIMER_INTERVAL_MS);
|
|
165112
|
+
if (typeof timer === "object" && "unref" in timer) {
|
|
165113
|
+
timer.unref();
|
|
165114
|
+
}
|
|
165115
|
+
activeTimer = timer;
|
|
165116
|
+
} else if (isNewRegistration) {
|
|
165117
|
+
sweepProject(args, "startup");
|
|
165118
|
+
}
|
|
165119
|
+
return () => {
|
|
165120
|
+
registeredProjects.delete(args.directory);
|
|
165121
|
+
log(`[dreamer] unregistered project ${args.directory} (remaining=${registeredProjects.size})`);
|
|
165122
|
+
if (registeredProjects.size === 0 && activeTimer) {
|
|
165123
|
+
clearInterval(activeTimer);
|
|
165124
|
+
activeTimer = null;
|
|
165125
|
+
log("[dreamer] stopped dream schedule timer (no projects left)");
|
|
165126
|
+
}
|
|
165127
|
+
};
|
|
165128
|
+
}
|
|
165129
|
+
function runTick(origin) {
|
|
165130
|
+
log(`[dreamer] timer tick (${origin}) \u2014 projects=${registeredProjects.size}`);
|
|
165131
|
+
try {
|
|
165132
|
+
const anyEmbeddingEnabled = Array.from(registeredProjects.values()).some((r) => r.memoryEnabled && r.embeddingConfig.provider !== "off");
|
|
165133
|
+
if (anyEmbeddingEnabled) {
|
|
165134
|
+
const first = registeredProjects.values().next().value;
|
|
165135
|
+
if (first) {
|
|
165136
|
+
embedAllUnembeddedMemories(openDatabase(), first.embeddingConfig).then((embeddedCount) => {
|
|
164178
165137
|
if (embeddedCount > 0) {
|
|
164179
165138
|
log(`[magic-context] proactively embedded ${embeddedCount} ${embeddedCount === 1 ? "memory" : "memories"} across all projects`);
|
|
164180
165139
|
}
|
|
@@ -164182,41 +165141,65 @@ function startDreamScheduleTimer(args) {
|
|
|
164182
165141
|
log("[magic-context] periodic memory embedding sweep failed:", error48);
|
|
164183
165142
|
});
|
|
164184
165143
|
}
|
|
164185
|
-
if (!dreamingEnabled || !dreamerConfig?.schedule?.trim()) {
|
|
164186
|
-
log("[dreamer] timer tick \u2014 dreaming disabled, skipping schedule check");
|
|
164187
|
-
return;
|
|
164188
|
-
}
|
|
164189
|
-
const db = openDatabase();
|
|
164190
|
-
log(`[dreamer] timer tick \u2014 checking schedule window "${dreamerConfig.schedule}"`);
|
|
164191
|
-
checkScheduleAndEnqueue(db, dreamerConfig.schedule);
|
|
164192
|
-
processDreamQueue({
|
|
164193
|
-
db,
|
|
164194
|
-
client,
|
|
164195
|
-
tasks: dreamerConfig.tasks,
|
|
164196
|
-
taskTimeoutMinutes: dreamerConfig.task_timeout_minutes,
|
|
164197
|
-
maxRuntimeMinutes: dreamerConfig.max_runtime_minutes,
|
|
164198
|
-
experimentalUserMemories,
|
|
164199
|
-
experimentalPinKeyFiles
|
|
164200
|
-
}).catch((error48) => {
|
|
164201
|
-
log("[dreamer] timer-triggered queue processing failed:", error48);
|
|
164202
|
-
});
|
|
164203
|
-
} catch (error48) {
|
|
164204
|
-
log("[magic-context] timer-triggered maintenance check failed:", error48);
|
|
164205
165144
|
}
|
|
164206
|
-
|
|
164207
|
-
|
|
164208
|
-
|
|
165145
|
+
for (const reg of registeredProjects.values()) {
|
|
165146
|
+
sweepProject(reg, origin);
|
|
165147
|
+
}
|
|
165148
|
+
} catch (error48) {
|
|
165149
|
+
log("[magic-context] timer-triggered maintenance check failed:", error48);
|
|
165150
|
+
}
|
|
165151
|
+
}
|
|
165152
|
+
async function sweepProject(reg, origin) {
|
|
165153
|
+
const dreamingEnabled = Boolean(reg.dreamerConfig?.enabled && reg.dreamerConfig.schedule?.trim());
|
|
165154
|
+
const commitIndexingEnabled = reg.gitCommitIndexing?.enabled === true;
|
|
165155
|
+
if (commitIndexingEnabled && reg.gitCommitIndexing) {
|
|
165156
|
+
await sweepGitCommits({
|
|
165157
|
+
directory: reg.directory,
|
|
165158
|
+
gitCommitIndexing: reg.gitCommitIndexing,
|
|
165159
|
+
embeddingConfig: reg.embeddingConfig
|
|
165160
|
+
});
|
|
165161
|
+
}
|
|
165162
|
+
if (!dreamingEnabled || !reg.dreamerConfig?.schedule?.trim()) {
|
|
165163
|
+
return;
|
|
165164
|
+
}
|
|
165165
|
+
try {
|
|
165166
|
+
const db = openDatabase();
|
|
165167
|
+
log(`[dreamer] timer tick (${origin}) ${reg.directory} \u2014 checking schedule window "${reg.dreamerConfig.schedule}"`);
|
|
165168
|
+
checkScheduleAndEnqueue(db, reg.dreamerConfig.schedule);
|
|
165169
|
+
await processDreamQueue({
|
|
165170
|
+
db,
|
|
165171
|
+
client: reg.client,
|
|
165172
|
+
tasks: reg.dreamerConfig.tasks,
|
|
165173
|
+
taskTimeoutMinutes: reg.dreamerConfig.task_timeout_minutes,
|
|
165174
|
+
maxRuntimeMinutes: reg.dreamerConfig.max_runtime_minutes,
|
|
165175
|
+
experimentalUserMemories: reg.experimentalUserMemories,
|
|
165176
|
+
experimentalPinKeyFiles: reg.experimentalPinKeyFiles
|
|
165177
|
+
});
|
|
165178
|
+
} catch (error48) {
|
|
165179
|
+
log(`[dreamer] timer-triggered queue processing failed for ${reg.directory}:`, error48);
|
|
165180
|
+
}
|
|
165181
|
+
}
|
|
165182
|
+
async function sweepGitCommits(args) {
|
|
165183
|
+
const { directory, gitCommitIndexing, embeddingConfig: embeddingConfig2 } = args;
|
|
165184
|
+
const startedAt = Date.now();
|
|
165185
|
+
log(`[git-commits] sweep starting for ${directory} (sinceDays=${gitCommitIndexing.since_days} maxCommits=${gitCommitIndexing.max_commits} embedding=${embeddingConfig2.provider})`);
|
|
165186
|
+
try {
|
|
165187
|
+
const db = openDatabase();
|
|
165188
|
+
const projectPath = resolveProjectIdentity(directory);
|
|
165189
|
+
const result = await indexCommitsForProject(db, projectPath, directory, embeddingConfig2, {
|
|
165190
|
+
sinceDays: gitCommitIndexing.since_days,
|
|
165191
|
+
maxCommits: gitCommitIndexing.max_commits
|
|
165192
|
+
});
|
|
165193
|
+
let drainedEmbeddings = 0;
|
|
165194
|
+
if (embeddingConfig2.provider !== "off" && result.embedded > 0) {
|
|
165195
|
+
drainedEmbeddings = await embedUnembeddedCommits(db, projectPath, embeddingConfig2);
|
|
165196
|
+
}
|
|
165197
|
+
const elapsedMs = Date.now() - startedAt;
|
|
165198
|
+
log(`[git-commits] sweep finished for ${projectPath} in ${elapsedMs}ms: scanned=${result.scanned} inserted=${result.inserted} updated=${result.updated} evicted=${result.evicted} embedded=${result.embedded} drained=${drainedEmbeddings}`);
|
|
165199
|
+
} catch (error48) {
|
|
165200
|
+
const elapsedMs = Date.now() - startedAt;
|
|
165201
|
+
log(`[git-commits] sweep failed for ${directory} after ${elapsedMs}ms: ${error48 instanceof Error ? error48.message : String(error48)}`);
|
|
164209
165202
|
}
|
|
164210
|
-
const cleanup = () => {
|
|
164211
|
-
clearInterval(timer);
|
|
164212
|
-
activeTimer = null;
|
|
164213
|
-
activeCleanup = null;
|
|
164214
|
-
log("[dreamer] stopped dream schedule timer");
|
|
164215
|
-
};
|
|
164216
|
-
activeTimer = timer;
|
|
164217
|
-
activeCleanup = cleanup;
|
|
164218
|
-
log(`[dreamer] started independent schedule timer (every ${DREAM_TIMER_INTERVAL_MS / 60000}m)`);
|
|
164219
|
-
return cleanup;
|
|
164220
165203
|
}
|
|
164221
165204
|
|
|
164222
165205
|
// src/plugin/event.ts
|
|
@@ -164243,21 +165226,23 @@ function createCompactionHandler() {
|
|
|
164243
165226
|
};
|
|
164244
165227
|
}
|
|
164245
165228
|
// src/hooks/magic-context/event-resolvers.ts
|
|
165229
|
+
init_storage_meta_persisted();
|
|
164246
165230
|
init_logger();
|
|
164247
165231
|
init_models_dev_cache();
|
|
164248
165232
|
var DEFAULT_CONTEXT_LIMIT = 128000;
|
|
164249
165233
|
var MAX_EXECUTE_THRESHOLD = 80;
|
|
164250
|
-
function resolveContextLimit(providerID, modelID) {
|
|
164251
|
-
|
|
164252
|
-
|
|
164253
|
-
|
|
164254
|
-
|
|
164255
|
-
|
|
164256
|
-
|
|
164257
|
-
|
|
164258
|
-
|
|
165234
|
+
function resolveContextLimit(providerID, modelID, ctx) {
|
|
165235
|
+
const fromModelsDev = providerID && modelID ? getModelsDevContextLimit(providerID, modelID) : undefined;
|
|
165236
|
+
const baseline = fromModelsDev ?? DEFAULT_CONTEXT_LIMIT;
|
|
165237
|
+
if (ctx?.db && ctx.sessionID) {
|
|
165238
|
+
try {
|
|
165239
|
+
const overflow = getOverflowState(ctx.db, ctx.sessionID);
|
|
165240
|
+
if (overflow.detectedContextLimit > 0 && overflow.detectedContextLimit < baseline) {
|
|
165241
|
+
return overflow.detectedContextLimit;
|
|
165242
|
+
}
|
|
165243
|
+
} catch {}
|
|
164259
165244
|
}
|
|
164260
|
-
return
|
|
165245
|
+
return baseline;
|
|
164261
165246
|
}
|
|
164262
165247
|
function resolveCacheTtl(cacheTtl, modelKey) {
|
|
164263
165248
|
if (typeof cacheTtl === "string") {
|
|
@@ -165197,6 +166182,112 @@ ${snap.error}`;
|
|
|
165197
166182
|
// src/hooks/magic-context/hook.ts
|
|
165198
166183
|
init_derive_budgets();
|
|
165199
166184
|
|
|
166185
|
+
// src/features/magic-context/overflow-detection.ts
|
|
166186
|
+
var OVERFLOW_PATTERNS = [
|
|
166187
|
+
/prompt is too long/i,
|
|
166188
|
+
/input is too long for requested model/i,
|
|
166189
|
+
/exceeds the context window/i,
|
|
166190
|
+
/input token count.*exceeds the maximum/i,
|
|
166191
|
+
/maximum prompt length is \d+/i,
|
|
166192
|
+
/reduce the length of the messages/i,
|
|
166193
|
+
/maximum context length is \d+ tokens/i,
|
|
166194
|
+
/exceeds the limit of \d+/i,
|
|
166195
|
+
/exceeds the available context size/i,
|
|
166196
|
+
/greater than the context length/i,
|
|
166197
|
+
/context window exceeds limit/i,
|
|
166198
|
+
/exceeded model token limit/i,
|
|
166199
|
+
/context[_ ]length[_ ]exceeded/i,
|
|
166200
|
+
/request entity too large/i,
|
|
166201
|
+
/context length is only \d+ tokens/i,
|
|
166202
|
+
/input length.*exceeds.*context length/i,
|
|
166203
|
+
/prompt too long; exceeded (?:max )?context length/i,
|
|
166204
|
+
/too large for model with \d+ maximum context length/i,
|
|
166205
|
+
/model_context_window_exceeded/i,
|
|
166206
|
+
/context size has been exceeded/i
|
|
166207
|
+
];
|
|
166208
|
+
var LIMIT_EXTRACTION_PATTERNS = [
|
|
166209
|
+
/maximum prompt length is (\d+)/i,
|
|
166210
|
+
/maximum context length is (\d+) tokens?/i,
|
|
166211
|
+
/context length is only (\d+) tokens?/i,
|
|
166212
|
+
/exceeds the limit of (\d+)/i,
|
|
166213
|
+
/too large for model with (\d+) maximum context length/i,
|
|
166214
|
+
/context size.*(\d+) tokens?/i,
|
|
166215
|
+
/exceeds? the context length of (\d+)/i,
|
|
166216
|
+
/max(?:imum)?.*context.*?(\d+)/i
|
|
166217
|
+
];
|
|
166218
|
+
var MIN_PLAUSIBLE_LIMIT = 1024;
|
|
166219
|
+
var MAX_PLAUSIBLE_LIMIT = 1e7;
|
|
166220
|
+
function extractErrorMessage(error48) {
|
|
166221
|
+
if (!error48)
|
|
166222
|
+
return "";
|
|
166223
|
+
if (typeof error48 === "string")
|
|
166224
|
+
return error48;
|
|
166225
|
+
if (typeof error48 === "object") {
|
|
166226
|
+
const obj = error48;
|
|
166227
|
+
const nested = obj.error;
|
|
166228
|
+
if (nested && typeof nested.message === "string" && nested.message.length > 0) {
|
|
166229
|
+
return nested.message;
|
|
166230
|
+
}
|
|
166231
|
+
}
|
|
166232
|
+
if (error48 instanceof Error)
|
|
166233
|
+
return error48.message;
|
|
166234
|
+
if (typeof error48 === "object") {
|
|
166235
|
+
const obj = error48;
|
|
166236
|
+
if (typeof obj.message === "string")
|
|
166237
|
+
return obj.message;
|
|
166238
|
+
if (typeof obj.responseBody === "string")
|
|
166239
|
+
return obj.responseBody;
|
|
166240
|
+
try {
|
|
166241
|
+
return JSON.stringify(error48);
|
|
166242
|
+
} catch {
|
|
166243
|
+
return String(error48);
|
|
166244
|
+
}
|
|
166245
|
+
}
|
|
166246
|
+
return String(error48);
|
|
166247
|
+
}
|
|
166248
|
+
function detectOverflow(error48) {
|
|
166249
|
+
const message = extractErrorMessage(error48);
|
|
166250
|
+
if (!message) {
|
|
166251
|
+
return { isOverflow: false };
|
|
166252
|
+
}
|
|
166253
|
+
const hasStatus413 = /\b413\b/.test(message) && /(entity|payload|context|prompt)/i.test(message);
|
|
166254
|
+
let matched;
|
|
166255
|
+
for (const pattern of OVERFLOW_PATTERNS) {
|
|
166256
|
+
if (pattern.test(message)) {
|
|
166257
|
+
matched = pattern;
|
|
166258
|
+
break;
|
|
166259
|
+
}
|
|
166260
|
+
}
|
|
166261
|
+
if (!matched && !hasStatus413) {
|
|
166262
|
+
return { isOverflow: false };
|
|
166263
|
+
}
|
|
166264
|
+
const reportedLimit = parseReportedLimit(message);
|
|
166265
|
+
return {
|
|
166266
|
+
isOverflow: true,
|
|
166267
|
+
reportedLimit,
|
|
166268
|
+
matchedPattern: matched?.source
|
|
166269
|
+
};
|
|
166270
|
+
}
|
|
166271
|
+
function parseReportedLimit(message) {
|
|
166272
|
+
if (!message)
|
|
166273
|
+
return;
|
|
166274
|
+
for (const pattern of LIMIT_EXTRACTION_PATTERNS) {
|
|
166275
|
+
const match = message.match(pattern);
|
|
166276
|
+
if (!match)
|
|
166277
|
+
continue;
|
|
166278
|
+
const raw = match[1];
|
|
166279
|
+
if (!raw)
|
|
166280
|
+
continue;
|
|
166281
|
+
const value = Number.parseInt(raw, 10);
|
|
166282
|
+
if (!Number.isFinite(value))
|
|
166283
|
+
continue;
|
|
166284
|
+
if (value < MIN_PLAUSIBLE_LIMIT || value > MAX_PLAUSIBLE_LIMIT)
|
|
166285
|
+
continue;
|
|
166286
|
+
return value;
|
|
166287
|
+
}
|
|
166288
|
+
return;
|
|
166289
|
+
}
|
|
166290
|
+
|
|
165200
166291
|
// src/hooks/magic-context/event-handler.ts
|
|
165201
166292
|
init_storage();
|
|
165202
166293
|
init_storage_meta_persisted();
|
|
@@ -165255,9 +166346,18 @@ function getMessageUpdatedAssistantInfo(properties) {
|
|
|
165255
166346
|
read: typeof cache?.read === "number" ? cache.read : undefined,
|
|
165256
166347
|
write: typeof cache?.write === "number" ? cache.write : undefined
|
|
165257
166348
|
}
|
|
165258
|
-
}
|
|
166349
|
+
},
|
|
166350
|
+
error: info.error !== undefined ? info.error : undefined
|
|
165259
166351
|
};
|
|
165260
166352
|
}
|
|
166353
|
+
function getSessionErrorInfo(properties) {
|
|
166354
|
+
if (!isRecord(properties))
|
|
166355
|
+
return null;
|
|
166356
|
+
const sessionID = properties.sessionID;
|
|
166357
|
+
if (typeof sessionID !== "string" || sessionID.length === 0)
|
|
166358
|
+
return null;
|
|
166359
|
+
return { sessionID, error: properties.error };
|
|
166360
|
+
}
|
|
165261
166361
|
function getMessageRemovedInfo(properties) {
|
|
165262
166362
|
if (!isRecord(properties)) {
|
|
165263
166363
|
return null;
|
|
@@ -165436,6 +166536,18 @@ init_inject_compartments();
|
|
|
165436
166536
|
init_note_nudger();
|
|
165437
166537
|
init_read_session_chunk();
|
|
165438
166538
|
init_read_session_formatting();
|
|
166539
|
+
|
|
166540
|
+
// src/hooks/magic-context/reasoning-capability.ts
|
|
166541
|
+
init_models_dev_cache();
|
|
166542
|
+
function modelRequiresInterleavedReasoning(model) {
|
|
166543
|
+
if (!model?.providerID || !model?.modelID) {
|
|
166544
|
+
return false;
|
|
166545
|
+
}
|
|
166546
|
+
const field = getModelsDevInterleavedField(model.providerID, model.modelID);
|
|
166547
|
+
return typeof field === "string" && field.length > 0;
|
|
166548
|
+
}
|
|
166549
|
+
|
|
166550
|
+
// src/hooks/magic-context/transform.ts
|
|
165439
166551
|
init_send_session_notification();
|
|
165440
166552
|
|
|
165441
166553
|
// src/hooks/magic-context/strip-content.ts
|
|
@@ -165560,7 +166672,9 @@ function stripDroppedPlaceholderMessages(messages) {
|
|
|
165560
166672
|
}
|
|
165561
166673
|
return stripped;
|
|
165562
166674
|
}
|
|
165563
|
-
function replayClearedReasoning(messages, reasoningByMessage, messageTagNumbers, persistedWatermark) {
|
|
166675
|
+
function replayClearedReasoning(messages, reasoningByMessage, messageTagNumbers, persistedWatermark, skipTypedReasoningCleanup = false) {
|
|
166676
|
+
if (skipTypedReasoningCleanup)
|
|
166677
|
+
return 0;
|
|
165564
166678
|
if (persistedWatermark <= 0)
|
|
165565
166679
|
return 0;
|
|
165566
166680
|
let cleared = 0;
|
|
@@ -165606,7 +166720,9 @@ function replayStrippedInlineThinking(messages, messageTagNumbers, persistedWate
|
|
|
165606
166720
|
}
|
|
165607
166721
|
return stripped;
|
|
165608
166722
|
}
|
|
165609
|
-
function clearOldReasoning(messages, reasoningByMessage, messageTagNumbers, clearReasoningAge) {
|
|
166723
|
+
function clearOldReasoning(messages, reasoningByMessage, messageTagNumbers, clearReasoningAge, skipTypedReasoningCleanup = false) {
|
|
166724
|
+
if (skipTypedReasoningCleanup)
|
|
166725
|
+
return 0;
|
|
165610
166726
|
const maxTag = findMaxTag(messageTagNumbers);
|
|
165611
166727
|
if (maxTag === 0)
|
|
165612
166728
|
return 0;
|
|
@@ -165641,7 +166757,9 @@ function findMaxTag(messageTagNumbers) {
|
|
|
165641
166757
|
return max;
|
|
165642
166758
|
}
|
|
165643
166759
|
var CLEARED_REASONING_TYPES = new Set(["thinking", "reasoning"]);
|
|
165644
|
-
function stripClearedReasoning(messages) {
|
|
166760
|
+
function stripClearedReasoning(messages, skipTypedReasoningCleanup = false) {
|
|
166761
|
+
if (skipTypedReasoningCleanup)
|
|
166762
|
+
return 0;
|
|
165645
166763
|
let stripped = 0;
|
|
165646
166764
|
for (const message of messages) {
|
|
165647
166765
|
if (message.info.role !== "assistant")
|
|
@@ -166870,6 +167988,571 @@ function applyContextNudge(messages, nudge, nudgePlacements, sessionId) {
|
|
|
166870
167988
|
}
|
|
166871
167989
|
}
|
|
166872
167990
|
|
|
167991
|
+
// src/features/magic-context/search.ts
|
|
167992
|
+
init_read_session_chunk();
|
|
167993
|
+
init_logger();
|
|
167994
|
+
init_memory();
|
|
167995
|
+
init_embedding();
|
|
167996
|
+
init_storage_memory_fts();
|
|
167997
|
+
init_message_index();
|
|
167998
|
+
var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
|
|
167999
|
+
var FTS_SEMANTIC_CANDIDATE_LIMIT = 50;
|
|
168000
|
+
var SEMANTIC_WEIGHT = 0.7;
|
|
168001
|
+
var FTS_WEIGHT = 0.3;
|
|
168002
|
+
var SINGLE_SOURCE_PENALTY = 0.8;
|
|
168003
|
+
var RESULT_PREVIEW_LIMIT = 220;
|
|
168004
|
+
var MEMORY_SOURCE_BOOST = 1.3;
|
|
168005
|
+
var MESSAGE_SOURCE_BOOST = 1.15;
|
|
168006
|
+
var GIT_COMMIT_SOURCE_BOOST = 1.2;
|
|
168007
|
+
var messageSearchStatements = new WeakMap;
|
|
168008
|
+
function normalizeLimit(limit) {
|
|
168009
|
+
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
168010
|
+
return DEFAULT_UNIFIED_SEARCH_LIMIT;
|
|
168011
|
+
}
|
|
168012
|
+
return Math.max(1, Math.floor(limit));
|
|
168013
|
+
}
|
|
168014
|
+
function normalizeCosineScore(score) {
|
|
168015
|
+
if (!Number.isFinite(score)) {
|
|
168016
|
+
return 0;
|
|
168017
|
+
}
|
|
168018
|
+
return Math.min(1, Math.max(0, score));
|
|
168019
|
+
}
|
|
168020
|
+
function previewText(text) {
|
|
168021
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
168022
|
+
if (normalized.length <= RESULT_PREVIEW_LIMIT) {
|
|
168023
|
+
return normalized;
|
|
168024
|
+
}
|
|
168025
|
+
return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}\u2026`;
|
|
168026
|
+
}
|
|
168027
|
+
function getMessageSearchStatement(db) {
|
|
168028
|
+
let stmt = messageSearchStatements.get(db);
|
|
168029
|
+
if (!stmt) {
|
|
168030
|
+
stmt = db.prepare("SELECT message_ordinal AS messageOrdinal, message_id AS messageId, role, content FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ? ORDER BY bm25(message_history_fts), CAST(message_ordinal AS INTEGER) ASC LIMIT ?");
|
|
168031
|
+
messageSearchStatements.set(db, stmt);
|
|
168032
|
+
}
|
|
168033
|
+
return stmt;
|
|
168034
|
+
}
|
|
168035
|
+
function getMessageOrdinal(value) {
|
|
168036
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
168037
|
+
return value;
|
|
168038
|
+
}
|
|
168039
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
168040
|
+
const parsed = Number.parseInt(value, 10);
|
|
168041
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
168042
|
+
}
|
|
168043
|
+
return null;
|
|
168044
|
+
}
|
|
168045
|
+
async function getSemanticScores(args) {
|
|
168046
|
+
const semanticScores = new Map;
|
|
168047
|
+
if (!args.embeddingEnabled || !args.isEmbeddingRuntimeEnabled() || args.memories.length === 0) {
|
|
168048
|
+
return semanticScores;
|
|
168049
|
+
}
|
|
168050
|
+
const queryEmbedding = await args.embedQuery(args.query, args.signal);
|
|
168051
|
+
if (!queryEmbedding) {
|
|
168052
|
+
return semanticScores;
|
|
168053
|
+
}
|
|
168054
|
+
const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
|
|
168055
|
+
const embeddings = await ensureMemoryEmbeddings({
|
|
168056
|
+
db: args.db,
|
|
168057
|
+
memories: args.memories,
|
|
168058
|
+
existingEmbeddings: cachedEmbeddings
|
|
168059
|
+
});
|
|
168060
|
+
for (const memory of args.memories) {
|
|
168061
|
+
const memoryEmbedding = embeddings.get(memory.id);
|
|
168062
|
+
if (!memoryEmbedding) {
|
|
168063
|
+
continue;
|
|
168064
|
+
}
|
|
168065
|
+
semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(queryEmbedding, memoryEmbedding)));
|
|
168066
|
+
}
|
|
168067
|
+
return semanticScores;
|
|
168068
|
+
}
|
|
168069
|
+
function getFtsMatches(args) {
|
|
168070
|
+
try {
|
|
168071
|
+
return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
|
|
168072
|
+
} catch (error48) {
|
|
168073
|
+
log(`[search] FTS query failed for "${args.query}": ${error48 instanceof Error ? error48.message : String(error48)}`);
|
|
168074
|
+
return [];
|
|
168075
|
+
}
|
|
168076
|
+
}
|
|
168077
|
+
function getFtsScores(matches) {
|
|
168078
|
+
return new Map(matches.map((memory, rank) => [memory.id, 1 / (rank + 1)]));
|
|
168079
|
+
}
|
|
168080
|
+
function selectSemanticCandidates(args) {
|
|
168081
|
+
if (args.ftsMatches.length === 0) {
|
|
168082
|
+
return args.memories;
|
|
168083
|
+
}
|
|
168084
|
+
const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
|
|
168085
|
+
const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
|
|
168086
|
+
if (cachedEmbeddings) {
|
|
168087
|
+
for (const memoryId of cachedEmbeddings.keys()) {
|
|
168088
|
+
candidateIds.add(memoryId);
|
|
168089
|
+
}
|
|
168090
|
+
}
|
|
168091
|
+
return args.memories.filter((memory) => candidateIds.has(memory.id));
|
|
168092
|
+
}
|
|
168093
|
+
function mergeMemoryResults(args) {
|
|
168094
|
+
const memoryById = new Map(args.memories.map((memory) => [memory.id, memory]));
|
|
168095
|
+
const candidateIds = new Set([...args.semanticScores.keys(), ...args.ftsScores.keys()]);
|
|
168096
|
+
const results = [];
|
|
168097
|
+
for (const id of candidateIds) {
|
|
168098
|
+
if (args.visibleMemoryIds?.has(id)) {
|
|
168099
|
+
continue;
|
|
168100
|
+
}
|
|
168101
|
+
const memory = memoryById.get(id);
|
|
168102
|
+
if (!memory) {
|
|
168103
|
+
continue;
|
|
168104
|
+
}
|
|
168105
|
+
const semanticScore = args.semanticScores.get(id);
|
|
168106
|
+
const ftsScore = args.ftsScores.get(id);
|
|
168107
|
+
let score = 0;
|
|
168108
|
+
let matchType = "fts";
|
|
168109
|
+
if (semanticScore !== undefined && ftsScore !== undefined) {
|
|
168110
|
+
score = SEMANTIC_WEIGHT * semanticScore + FTS_WEIGHT * ftsScore;
|
|
168111
|
+
matchType = "hybrid";
|
|
168112
|
+
} else if (semanticScore !== undefined) {
|
|
168113
|
+
score = semanticScore * SINGLE_SOURCE_PENALTY;
|
|
168114
|
+
matchType = "semantic";
|
|
168115
|
+
} else if (ftsScore !== undefined) {
|
|
168116
|
+
score = ftsScore * SINGLE_SOURCE_PENALTY;
|
|
168117
|
+
matchType = "fts";
|
|
168118
|
+
}
|
|
168119
|
+
if (score <= 0) {
|
|
168120
|
+
continue;
|
|
168121
|
+
}
|
|
168122
|
+
results.push({
|
|
168123
|
+
source: "memory",
|
|
168124
|
+
content: previewText(memory.content),
|
|
168125
|
+
score,
|
|
168126
|
+
memoryId: memory.id,
|
|
168127
|
+
category: memory.category,
|
|
168128
|
+
matchType
|
|
168129
|
+
});
|
|
168130
|
+
}
|
|
168131
|
+
return results.sort((left, right) => {
|
|
168132
|
+
if (right.score !== left.score) {
|
|
168133
|
+
return right.score - left.score;
|
|
168134
|
+
}
|
|
168135
|
+
return left.memoryId - right.memoryId;
|
|
168136
|
+
}).slice(0, args.limit);
|
|
168137
|
+
}
|
|
168138
|
+
async function searchMemories(args) {
|
|
168139
|
+
if (!args.memoryEnabled) {
|
|
168140
|
+
return [];
|
|
168141
|
+
}
|
|
168142
|
+
const memories = getMemoriesByProject(args.db, args.projectPath);
|
|
168143
|
+
if (memories.length === 0) {
|
|
168144
|
+
return [];
|
|
168145
|
+
}
|
|
168146
|
+
const ftsMatches = getFtsMatches({
|
|
168147
|
+
db: args.db,
|
|
168148
|
+
projectPath: args.projectPath,
|
|
168149
|
+
query: args.query,
|
|
168150
|
+
limit: FTS_SEMANTIC_CANDIDATE_LIMIT
|
|
168151
|
+
});
|
|
168152
|
+
const ftsScores = getFtsScores(ftsMatches);
|
|
168153
|
+
const semanticCandidates = selectSemanticCandidates({
|
|
168154
|
+
memories,
|
|
168155
|
+
projectPath: args.projectPath,
|
|
168156
|
+
ftsMatches
|
|
168157
|
+
});
|
|
168158
|
+
const semanticScores = await getSemanticScores({
|
|
168159
|
+
db: args.db,
|
|
168160
|
+
projectPath: args.projectPath,
|
|
168161
|
+
query: args.query,
|
|
168162
|
+
memories: semanticCandidates,
|
|
168163
|
+
embeddingEnabled: args.embeddingEnabled,
|
|
168164
|
+
embedQuery: args.embedQuery,
|
|
168165
|
+
isEmbeddingRuntimeEnabled: args.isEmbeddingRuntimeEnabled,
|
|
168166
|
+
signal: args.signal
|
|
168167
|
+
});
|
|
168168
|
+
return mergeMemoryResults({
|
|
168169
|
+
memories,
|
|
168170
|
+
semanticScores,
|
|
168171
|
+
ftsScores,
|
|
168172
|
+
limit: args.limit,
|
|
168173
|
+
visibleMemoryIds: args.visibleMemoryIds
|
|
168174
|
+
});
|
|
168175
|
+
}
|
|
168176
|
+
function linearDecayScore(rank, total) {
|
|
168177
|
+
if (total <= 0)
|
|
168178
|
+
return 0;
|
|
168179
|
+
return Math.max(0, 1 - rank / total);
|
|
168180
|
+
}
|
|
168181
|
+
function searchMessages(args) {
|
|
168182
|
+
ensureMessagesIndexed(args.db, args.sessionId, args.readMessages);
|
|
168183
|
+
const sanitizedQuery = sanitizeFtsQuery(args.query.trim());
|
|
168184
|
+
if (sanitizedQuery.length === 0) {
|
|
168185
|
+
return [];
|
|
168186
|
+
}
|
|
168187
|
+
const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
|
|
168188
|
+
const rows = getMessageSearchStatement(args.db).all(args.sessionId, sanitizedQuery, fetchLimit).map((row) => row);
|
|
168189
|
+
const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
|
|
168190
|
+
const filtered = rows.map((row) => {
|
|
168191
|
+
const messageOrdinal = getMessageOrdinal(row.messageOrdinal);
|
|
168192
|
+
if (messageOrdinal === null || typeof row.messageId !== "string" || typeof row.role !== "string" || typeof row.content !== "string") {
|
|
168193
|
+
return null;
|
|
168194
|
+
}
|
|
168195
|
+
if (cutoff !== null && messageOrdinal > cutoff) {
|
|
168196
|
+
return null;
|
|
168197
|
+
}
|
|
168198
|
+
return {
|
|
168199
|
+
messageOrdinal,
|
|
168200
|
+
messageId: row.messageId,
|
|
168201
|
+
role: row.role,
|
|
168202
|
+
content: row.content
|
|
168203
|
+
};
|
|
168204
|
+
}).filter((result) => result !== null).slice(0, args.limit);
|
|
168205
|
+
return filtered.map((row, rank) => ({
|
|
168206
|
+
source: "message",
|
|
168207
|
+
content: previewText(row.content),
|
|
168208
|
+
score: linearDecayScore(rank, filtered.length),
|
|
168209
|
+
messageOrdinal: row.messageOrdinal,
|
|
168210
|
+
messageId: row.messageId,
|
|
168211
|
+
role: row.role
|
|
168212
|
+
}));
|
|
168213
|
+
}
|
|
168214
|
+
function getSourceBoost(result) {
|
|
168215
|
+
switch (result.source) {
|
|
168216
|
+
case "memory":
|
|
168217
|
+
return MEMORY_SOURCE_BOOST;
|
|
168218
|
+
case "message":
|
|
168219
|
+
return MESSAGE_SOURCE_BOOST;
|
|
168220
|
+
case "git_commit":
|
|
168221
|
+
return GIT_COMMIT_SOURCE_BOOST;
|
|
168222
|
+
}
|
|
168223
|
+
}
|
|
168224
|
+
function compareUnifiedResults(left, right) {
|
|
168225
|
+
const leftEffective = left.score * getSourceBoost(left);
|
|
168226
|
+
const rightEffective = right.score * getSourceBoost(right);
|
|
168227
|
+
if (rightEffective !== leftEffective) {
|
|
168228
|
+
return rightEffective - leftEffective;
|
|
168229
|
+
}
|
|
168230
|
+
if (left.source === "memory" && right.source === "memory") {
|
|
168231
|
+
return left.memoryId - right.memoryId;
|
|
168232
|
+
}
|
|
168233
|
+
if (left.source === "message" && right.source === "message") {
|
|
168234
|
+
return left.messageOrdinal - right.messageOrdinal;
|
|
168235
|
+
}
|
|
168236
|
+
if (left.source === "git_commit" && right.source === "git_commit") {
|
|
168237
|
+
return right.committedAtMs - left.committedAtMs;
|
|
168238
|
+
}
|
|
168239
|
+
return 0;
|
|
168240
|
+
}
|
|
168241
|
+
function toGitCommitResult(hit) {
|
|
168242
|
+
return {
|
|
168243
|
+
source: "git_commit",
|
|
168244
|
+
content: previewText(hit.commit.message),
|
|
168245
|
+
score: hit.score,
|
|
168246
|
+
sha: hit.commit.sha,
|
|
168247
|
+
shortSha: hit.commit.shortSha,
|
|
168248
|
+
author: hit.commit.author,
|
|
168249
|
+
committedAtMs: hit.commit.committedAtMs,
|
|
168250
|
+
matchType: hit.matchType
|
|
168251
|
+
};
|
|
168252
|
+
}
|
|
168253
|
+
async function searchGitCommitsAsync(args) {
|
|
168254
|
+
if (args.limit <= 0)
|
|
168255
|
+
return [];
|
|
168256
|
+
let queryEmbedding = null;
|
|
168257
|
+
if (args.embeddingEnabled && args.isEmbeddingRuntimeEnabled()) {
|
|
168258
|
+
try {
|
|
168259
|
+
queryEmbedding = await args.embedQuery(args.query, args.signal);
|
|
168260
|
+
} catch (error48) {
|
|
168261
|
+
log(`[search] git commit query embedding failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
|
|
168262
|
+
}
|
|
168263
|
+
}
|
|
168264
|
+
const hits = searchGitCommitsSync(args.db, args.projectPath, args.query, {
|
|
168265
|
+
limit: args.limit,
|
|
168266
|
+
queryEmbedding
|
|
168267
|
+
});
|
|
168268
|
+
return hits.map(toGitCommitResult);
|
|
168269
|
+
}
|
|
168270
|
+
function resolveSources(sources) {
|
|
168271
|
+
if (sources === undefined) {
|
|
168272
|
+
return new Set(["memory", "message", "git_commit"]);
|
|
168273
|
+
}
|
|
168274
|
+
const set2 = new Set;
|
|
168275
|
+
for (const source of sources) {
|
|
168276
|
+
if (source === "memory" || source === "message" || source === "git_commit") {
|
|
168277
|
+
set2.add(source);
|
|
168278
|
+
}
|
|
168279
|
+
}
|
|
168280
|
+
return set2;
|
|
168281
|
+
}
|
|
168282
|
+
async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
|
|
168283
|
+
const trimmedQuery = query.trim();
|
|
168284
|
+
if (trimmedQuery.length === 0) {
|
|
168285
|
+
return [];
|
|
168286
|
+
}
|
|
168287
|
+
const limit = normalizeLimit(options.limit);
|
|
168288
|
+
const tierLimit = Math.max(limit * 3, DEFAULT_UNIFIED_SEARCH_LIMIT);
|
|
168289
|
+
const embeddingEnabled = options.embeddingEnabled ?? true;
|
|
168290
|
+
const embedQuery = options.embedQuery ?? embedText;
|
|
168291
|
+
const isEmbeddingRuntimeEnabled = options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
|
|
168292
|
+
const gitCommitsEnabled = options.gitCommitsEnabled ?? false;
|
|
168293
|
+
const activeSources = resolveSources(options.sources);
|
|
168294
|
+
const runMemory = activeSources.has("memory") && (options.memoryEnabled ?? true);
|
|
168295
|
+
const runMessages = activeSources.has("message");
|
|
168296
|
+
const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
|
|
168297
|
+
const [memoryResults, messageResults, gitCommitResults] = await Promise.all([
|
|
168298
|
+
runMemory ? searchMemories({
|
|
168299
|
+
db,
|
|
168300
|
+
projectPath,
|
|
168301
|
+
query: trimmedQuery,
|
|
168302
|
+
limit: tierLimit,
|
|
168303
|
+
memoryEnabled: true,
|
|
168304
|
+
embeddingEnabled,
|
|
168305
|
+
embedQuery,
|
|
168306
|
+
isEmbeddingRuntimeEnabled,
|
|
168307
|
+
visibleMemoryIds: options.visibleMemoryIds,
|
|
168308
|
+
signal: options.signal
|
|
168309
|
+
}) : Promise.resolve([]),
|
|
168310
|
+
runMessages ? Promise.resolve(searchMessages({
|
|
168311
|
+
db,
|
|
168312
|
+
sessionId,
|
|
168313
|
+
query: trimmedQuery,
|
|
168314
|
+
limit: tierLimit,
|
|
168315
|
+
readMessages: options.readMessages ?? readRawSessionMessages,
|
|
168316
|
+
maxOrdinal: options.maxMessageOrdinal
|
|
168317
|
+
})) : Promise.resolve([]),
|
|
168318
|
+
runGitCommits ? searchGitCommitsAsync({
|
|
168319
|
+
db,
|
|
168320
|
+
projectPath,
|
|
168321
|
+
query: trimmedQuery,
|
|
168322
|
+
limit: tierLimit,
|
|
168323
|
+
embeddingEnabled,
|
|
168324
|
+
embedQuery,
|
|
168325
|
+
isEmbeddingRuntimeEnabled,
|
|
168326
|
+
signal: options.signal
|
|
168327
|
+
}) : Promise.resolve([])
|
|
168328
|
+
]);
|
|
168329
|
+
const results = [...memoryResults, ...messageResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
|
|
168330
|
+
const countRetrievals = options.countRetrievals ?? true;
|
|
168331
|
+
if (countRetrievals) {
|
|
168332
|
+
const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
|
|
168333
|
+
if (memoryIds.length > 0) {
|
|
168334
|
+
db.transaction(() => {
|
|
168335
|
+
for (const memoryId of memoryIds) {
|
|
168336
|
+
updateMemoryRetrievalCount(db, memoryId);
|
|
168337
|
+
}
|
|
168338
|
+
})();
|
|
168339
|
+
}
|
|
168340
|
+
}
|
|
168341
|
+
return results;
|
|
168342
|
+
}
|
|
168343
|
+
|
|
168344
|
+
// src/hooks/magic-context/auto-search-runner.ts
|
|
168345
|
+
init_logger();
|
|
168346
|
+
|
|
168347
|
+
// src/hooks/magic-context/auto-search-hint.ts
|
|
168348
|
+
init_caveman();
|
|
168349
|
+
var MAX_FRAGMENTS = 3;
|
|
168350
|
+
var FRAGMENT_CHAR_CAP = 80;
|
|
168351
|
+
var MAX_HINT_CHARS = 800;
|
|
168352
|
+
var MS_PER_DAY2 = 24 * 60 * 60 * 1000;
|
|
168353
|
+
function truncate(text, limit) {
|
|
168354
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
168355
|
+
if (normalized.length <= limit)
|
|
168356
|
+
return normalized;
|
|
168357
|
+
return `${normalized.slice(0, Math.max(0, limit - 1)).trimEnd()}\u2026`;
|
|
168358
|
+
}
|
|
168359
|
+
function formatAge(committedAtMs) {
|
|
168360
|
+
const delta = Date.now() - committedAtMs;
|
|
168361
|
+
if (delta < 0)
|
|
168362
|
+
return "future";
|
|
168363
|
+
const days = Math.floor(delta / MS_PER_DAY2);
|
|
168364
|
+
if (days <= 0)
|
|
168365
|
+
return "today";
|
|
168366
|
+
if (days === 1)
|
|
168367
|
+
return "1d ago";
|
|
168368
|
+
if (days < 30)
|
|
168369
|
+
return `${days}d ago`;
|
|
168370
|
+
const months = Math.floor(days / 30);
|
|
168371
|
+
if (months === 1)
|
|
168372
|
+
return "1mo ago";
|
|
168373
|
+
if (months < 12)
|
|
168374
|
+
return `${months}mo ago`;
|
|
168375
|
+
const years = Math.floor(days / 365);
|
|
168376
|
+
return years === 1 ? "1y ago" : `${years}y ago`;
|
|
168377
|
+
}
|
|
168378
|
+
function renderFragment(result, charCap) {
|
|
168379
|
+
switch (result.source) {
|
|
168380
|
+
case "memory": {
|
|
168381
|
+
const compressed = cavemanCompress(result.content, "ultra");
|
|
168382
|
+
return truncate(compressed, charCap);
|
|
168383
|
+
}
|
|
168384
|
+
case "git_commit": {
|
|
168385
|
+
const subject = result.content.split(/\r?\n/)[0] ?? result.content;
|
|
168386
|
+
const body = truncate(subject, Math.max(10, charCap - 20));
|
|
168387
|
+
return `commit ${result.shortSha} ${formatAge(result.committedAtMs)}: ${body}`;
|
|
168388
|
+
}
|
|
168389
|
+
case "message": {
|
|
168390
|
+
const compressed = cavemanCompress(result.content, "ultra");
|
|
168391
|
+
return truncate(compressed, charCap);
|
|
168392
|
+
}
|
|
168393
|
+
}
|
|
168394
|
+
}
|
|
168395
|
+
function buildAutoSearchHint(results, options = {}) {
|
|
168396
|
+
const maxFragments = Math.max(1, options.maxFragments ?? MAX_FRAGMENTS);
|
|
168397
|
+
const fragmentCharCap = Math.max(20, options.fragmentCharCap ?? FRAGMENT_CHAR_CAP);
|
|
168398
|
+
const picks = results.slice(0, maxFragments);
|
|
168399
|
+
const lines = [];
|
|
168400
|
+
for (const result of picks) {
|
|
168401
|
+
const fragment = renderFragment(result, fragmentCharCap);
|
|
168402
|
+
if (fragment.length === 0)
|
|
168403
|
+
continue;
|
|
168404
|
+
lines.push(`- ${fragment}`);
|
|
168405
|
+
}
|
|
168406
|
+
if (lines.length === 0)
|
|
168407
|
+
return null;
|
|
168408
|
+
const header = lines.length === 1 ? "Your memory may contain 1 related fragment:" : `Your memory may contain ${lines.length} related fragments:`;
|
|
168409
|
+
const footer = "Run ctx_search to retrieve full context if relevant.";
|
|
168410
|
+
const body = [header, ...lines, footer].join(`
|
|
168411
|
+
`);
|
|
168412
|
+
const wrapped = `<ctx-search-hint>
|
|
168413
|
+
${body}
|
|
168414
|
+
</ctx-search-hint>`;
|
|
168415
|
+
if (wrapped.length > MAX_HINT_CHARS) {
|
|
168416
|
+
const overflow = wrapped.length - MAX_HINT_CHARS;
|
|
168417
|
+
const trimmedBody = body.slice(0, Math.max(0, body.length - overflow - 1)).trimEnd();
|
|
168418
|
+
return `<ctx-search-hint>
|
|
168419
|
+
${trimmedBody}\u2026
|
|
168420
|
+
</ctx-search-hint>`;
|
|
168421
|
+
}
|
|
168422
|
+
return wrapped;
|
|
168423
|
+
}
|
|
168424
|
+
|
|
168425
|
+
// src/hooks/magic-context/auto-search-runner.ts
|
|
168426
|
+
var autoSearchByTurn = new Map;
|
|
168427
|
+
var AUTO_SEARCH_TIMEOUT_MS = 3000;
|
|
168428
|
+
async function unifiedSearchWithTimeout(db, sessionId, projectPath, prompt, options, timeoutMs) {
|
|
168429
|
+
const controller = new AbortController;
|
|
168430
|
+
let timer;
|
|
168431
|
+
const timeoutPromise = new Promise((resolve3) => {
|
|
168432
|
+
timer = setTimeout(() => {
|
|
168433
|
+
controller.abort();
|
|
168434
|
+
resolve3(null);
|
|
168435
|
+
}, timeoutMs);
|
|
168436
|
+
});
|
|
168437
|
+
try {
|
|
168438
|
+
return await Promise.race([
|
|
168439
|
+
unifiedSearch(db, sessionId, projectPath, prompt, {
|
|
168440
|
+
...options,
|
|
168441
|
+
signal: controller.signal,
|
|
168442
|
+
countRetrievals: false
|
|
168443
|
+
}),
|
|
168444
|
+
timeoutPromise
|
|
168445
|
+
]);
|
|
168446
|
+
} finally {
|
|
168447
|
+
if (timer !== undefined)
|
|
168448
|
+
clearTimeout(timer);
|
|
168449
|
+
}
|
|
168450
|
+
}
|
|
168451
|
+
function collectUserPromptParts(message) {
|
|
168452
|
+
let collected = "";
|
|
168453
|
+
for (const part of message.parts) {
|
|
168454
|
+
const p = part;
|
|
168455
|
+
if (p.type === "text" && typeof p.text === "string") {
|
|
168456
|
+
collected += (collected.length > 0 ? `
|
|
168457
|
+
` : "") + p.text;
|
|
168458
|
+
}
|
|
168459
|
+
}
|
|
168460
|
+
return collected;
|
|
168461
|
+
}
|
|
168462
|
+
function hasStackedAugmentation(rawText) {
|
|
168463
|
+
return rawText.includes("<sidekick-augmentation>") || rawText.includes("<ctx-search-hint>") || rawText.includes("<ctx-search-auto>");
|
|
168464
|
+
}
|
|
168465
|
+
function extractUserPromptText(message) {
|
|
168466
|
+
return collectUserPromptParts(message).replace(/\u00A7\d+\u00A7\s*/g, "").replace(/<!--\s*\+[\d\s.hmdw]+\s*-->/g, "").replace(/<!--\s*OMO_INTERNAL_INITIATOR[\s\S]*?-->/g, "").replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").replace(/<ctx-search-hint>[\s\S]*?<\/ctx-search-hint>/g, "").replace(/<ctx-search-auto>[\s\S]*?<\/ctx-search-auto>/g, "").replace(/<instruction[^>]*>[\s\S]*?<\/instruction>/g, "").replace(/<sidekick-augmentation>[\s\S]*?<\/sidekick-augmentation>/g, "").replace(/[ \t]+\n/g, `
|
|
168467
|
+
`).replace(/\n{3,}/g, `
|
|
168468
|
+
|
|
168469
|
+
`).trim();
|
|
168470
|
+
}
|
|
168471
|
+
function findLatestMeaningfulUserMessage(messages) {
|
|
168472
|
+
for (let i = messages.length - 1;i >= 0; i -= 1) {
|
|
168473
|
+
const msg = messages[i];
|
|
168474
|
+
if (msg.info.role !== "user")
|
|
168475
|
+
continue;
|
|
168476
|
+
if (typeof msg.info.id !== "string")
|
|
168477
|
+
continue;
|
|
168478
|
+
for (const part of msg.parts) {
|
|
168479
|
+
const p = part;
|
|
168480
|
+
if (p.type === "text" && typeof p.text === "string" && p.text.trim().length > 0) {
|
|
168481
|
+
return msg;
|
|
168482
|
+
}
|
|
168483
|
+
}
|
|
168484
|
+
}
|
|
168485
|
+
return null;
|
|
168486
|
+
}
|
|
168487
|
+
async function runAutoSearchHint(args) {
|
|
168488
|
+
const { sessionId, db, messages, options } = args;
|
|
168489
|
+
if (!options.enabled)
|
|
168490
|
+
return;
|
|
168491
|
+
const userMsg = findLatestMeaningfulUserMessage(messages);
|
|
168492
|
+
if (!userMsg || typeof userMsg.info.id !== "string")
|
|
168493
|
+
return;
|
|
168494
|
+
const userMsgId = userMsg.info.id;
|
|
168495
|
+
const cached2 = autoSearchByTurn.get(sessionId);
|
|
168496
|
+
if (cached2 && cached2.messageId === userMsgId) {
|
|
168497
|
+
appendReminderToUserMessageById(messages, userMsgId, cached2.hint);
|
|
168498
|
+
return;
|
|
168499
|
+
}
|
|
168500
|
+
const rawPartsText = collectUserPromptParts(userMsg);
|
|
168501
|
+
if (hasStackedAugmentation(rawPartsText)) {
|
|
168502
|
+
sessionLog(sessionId, "auto-search: skipping \u2014 user message already carries augmentation/hint");
|
|
168503
|
+
autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
|
|
168504
|
+
return;
|
|
168505
|
+
}
|
|
168506
|
+
const rawPrompt = extractUserPromptText(userMsg);
|
|
168507
|
+
if (rawPrompt.length < options.minPromptChars) {
|
|
168508
|
+
autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
|
|
168509
|
+
return;
|
|
168510
|
+
}
|
|
168511
|
+
let results;
|
|
168512
|
+
try {
|
|
168513
|
+
const searchOptions = {
|
|
168514
|
+
limit: 10,
|
|
168515
|
+
memoryEnabled: options.memoryEnabled,
|
|
168516
|
+
embeddingEnabled: options.embeddingEnabled,
|
|
168517
|
+
gitCommitsEnabled: options.gitCommitsEnabled,
|
|
168518
|
+
visibleMemoryIds: options.visibleMemoryIds ?? null
|
|
168519
|
+
};
|
|
168520
|
+
results = await unifiedSearchWithTimeout(db, sessionId, options.projectPath, rawPrompt, searchOptions, AUTO_SEARCH_TIMEOUT_MS);
|
|
168521
|
+
} catch (error48) {
|
|
168522
|
+
log(`[auto-search] unified search failed for session ${sessionId}: ${error48 instanceof Error ? error48.message : String(error48)}`);
|
|
168523
|
+
autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
|
|
168524
|
+
return;
|
|
168525
|
+
}
|
|
168526
|
+
if (results === null) {
|
|
168527
|
+
sessionLog(sessionId, `auto-search: timed out after ${AUTO_SEARCH_TIMEOUT_MS}ms, skipping hint for this turn`);
|
|
168528
|
+
autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
|
|
168529
|
+
return;
|
|
168530
|
+
}
|
|
168531
|
+
if (results.length === 0) {
|
|
168532
|
+
autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
|
|
168533
|
+
return;
|
|
168534
|
+
}
|
|
168535
|
+
if (results[0].score < options.scoreThreshold) {
|
|
168536
|
+
sessionLog(sessionId, `auto-search: top score ${results[0].score.toFixed(3)} below threshold ${options.scoreThreshold}`);
|
|
168537
|
+
autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
|
|
168538
|
+
return;
|
|
168539
|
+
}
|
|
168540
|
+
const hintText = buildAutoSearchHint(results);
|
|
168541
|
+
if (!hintText) {
|
|
168542
|
+
autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: "" });
|
|
168543
|
+
return;
|
|
168544
|
+
}
|
|
168545
|
+
const payload = `
|
|
168546
|
+
|
|
168547
|
+
${hintText}`;
|
|
168548
|
+
autoSearchByTurn.set(sessionId, { messageId: userMsgId, hint: payload });
|
|
168549
|
+
appendReminderToUserMessageById(messages, userMsgId, payload);
|
|
168550
|
+
sessionLog(sessionId, `auto-search: attached hint to ${userMsgId} (${results.length} fragments, top score ${results[0].score.toFixed(3)})`);
|
|
168551
|
+
}
|
|
168552
|
+
function clearAutoSearchForSession(sessionId) {
|
|
168553
|
+
autoSearchByTurn.delete(sessionId);
|
|
168554
|
+
}
|
|
168555
|
+
|
|
166873
168556
|
// src/hooks/magic-context/transform-postprocess-phase.ts
|
|
166874
168557
|
init_compartment_runner();
|
|
166875
168558
|
|
|
@@ -167097,7 +168780,7 @@ function logTransformTiming(sessionId, stage, startMs, extra) {
|
|
|
167097
168780
|
}
|
|
167098
168781
|
|
|
167099
168782
|
// src/hooks/magic-context/transform-postprocess-phase.ts
|
|
167100
|
-
function runPostTransformPhase(args) {
|
|
168783
|
+
async function runPostTransformPhase(args) {
|
|
167101
168784
|
let didMutateFromPendingOperations = false;
|
|
167102
168785
|
const isExplicitFlush = args.flushedSessions.has(args.sessionId);
|
|
167103
168786
|
const alreadyRanThisTurn = args.currentTurnId !== null && args.lastHeuristicsTurnId.get(args.sessionId) === args.currentTurnId;
|
|
@@ -167157,8 +168840,8 @@ function runPostTransformPhase(args) {
|
|
|
167157
168840
|
logTransformTiming(args.sessionId, "watermarkCleanup", t6);
|
|
167158
168841
|
}
|
|
167159
168842
|
const t7 = performance.now();
|
|
167160
|
-
const clearedReasoning = clearOldReasoning(args.messages, args.reasoningByMessage, args.messageTagNumbers, args.clearReasoningAge);
|
|
167161
|
-
stripClearedReasoning(args.messages);
|
|
168843
|
+
const clearedReasoning = clearOldReasoning(args.messages, args.reasoningByMessage, args.messageTagNumbers, args.clearReasoningAge, args.skipTypedReasoningCleanup);
|
|
168844
|
+
stripClearedReasoning(args.messages, args.skipTypedReasoningCleanup);
|
|
167162
168845
|
const strippedInline = stripInlineThinking(args.messages, args.messageTagNumbers, args.clearReasoningAge);
|
|
167163
168846
|
if (clearedReasoning > 0 || strippedInline > 0) {
|
|
167164
168847
|
let maxTag = 0;
|
|
@@ -167172,6 +168855,7 @@ function runPostTransformPhase(args) {
|
|
|
167172
168855
|
updateSessionMeta(args.db, args.sessionId, {
|
|
167173
168856
|
clearedReasoningThroughTag: newWatermark
|
|
167174
168857
|
});
|
|
168858
|
+
args.sessionMeta.clearedReasoningThroughTag = newWatermark;
|
|
167175
168859
|
sessionLog(args.sessionId, `reasoning cleanup: cleared=${clearedReasoning} inlineStripped=${strippedInline} watermark=${currentWatermark}\u2192${newWatermark}`);
|
|
167176
168860
|
} else {
|
|
167177
168861
|
sessionLog(args.sessionId, `reasoning cleanup: cleared=${clearedReasoning} inlineStripped=${strippedInline} watermark=${currentWatermark} (unchanged)`);
|
|
@@ -167338,6 +169022,28 @@ function runPostTransformPhase(args) {
|
|
|
167338
169022
|
const anchoredMessageId = appendReminderToLatestUserMessage(args.messages, noteInstruction);
|
|
167339
169023
|
markNoteNudgeDelivered(args.db, args.sessionId, noteInstruction, anchoredMessageId);
|
|
167340
169024
|
}
|
|
169025
|
+
if (args.fullFeatureMode && args.autoSearch?.enabled && args.projectPath) {
|
|
169026
|
+
const visibleMemoryIds = getVisibleMemoryIds(args.db, args.sessionId) ?? undefined;
|
|
169027
|
+
try {
|
|
169028
|
+
await runAutoSearchHint({
|
|
169029
|
+
sessionId: args.sessionId,
|
|
169030
|
+
db: args.db,
|
|
169031
|
+
messages: args.messages,
|
|
169032
|
+
options: {
|
|
169033
|
+
enabled: true,
|
|
169034
|
+
scoreThreshold: args.autoSearch.scoreThreshold,
|
|
169035
|
+
minPromptChars: args.autoSearch.minPromptChars,
|
|
169036
|
+
projectPath: args.projectPath,
|
|
169037
|
+
memoryEnabled: args.autoSearch.memoryEnabled,
|
|
169038
|
+
embeddingEnabled: args.autoSearch.embeddingEnabled,
|
|
169039
|
+
gitCommitsEnabled: args.autoSearch.gitCommitsEnabled,
|
|
169040
|
+
visibleMemoryIds
|
|
169041
|
+
}
|
|
169042
|
+
});
|
|
169043
|
+
} catch (error48) {
|
|
169044
|
+
sessionLog(args.sessionId, "auto-search runner failed:", error48);
|
|
169045
|
+
}
|
|
169046
|
+
}
|
|
167341
169047
|
}
|
|
167342
169048
|
|
|
167343
169049
|
// src/hooks/magic-context/nudge-placement-store.ts
|
|
@@ -167449,10 +169155,20 @@ function createTransform(deps) {
|
|
|
167449
169155
|
deps.liveModelBySession.set(sessionId, lastAssistantModel);
|
|
167450
169156
|
updateSessionMeta(db, sessionId, {
|
|
167451
169157
|
lastContextPercentage: 0,
|
|
167452
|
-
lastInputTokens: 0
|
|
169158
|
+
lastInputTokens: 0,
|
|
169159
|
+
clearedReasoningThroughTag: 0
|
|
167453
169160
|
});
|
|
167454
169161
|
clearHistorianFailureState(db, sessionId);
|
|
169162
|
+
clearPersistedReasoningWatermark(db, sessionId);
|
|
169163
|
+
clearDetectedContextLimit(db, sessionId);
|
|
169164
|
+
clearEmergencyRecovery(db, sessionId);
|
|
167455
169165
|
deps.contextUsageMap.delete(sessionId);
|
|
169166
|
+
sessionMeta = {
|
|
169167
|
+
...sessionMeta,
|
|
169168
|
+
lastContextPercentage: 0,
|
|
169169
|
+
lastInputTokens: 0,
|
|
169170
|
+
clearedReasoningThroughTag: 0
|
|
169171
|
+
};
|
|
167456
169172
|
}
|
|
167457
169173
|
}
|
|
167458
169174
|
}
|
|
@@ -167474,7 +169190,21 @@ function createTransform(deps) {
|
|
|
167474
169190
|
sessionMeta = { ...sessionMeta, lastContextPercentage: 0, lastInputTokens: 0 };
|
|
167475
169191
|
}
|
|
167476
169192
|
}
|
|
167477
|
-
|
|
169193
|
+
let contextUsageEarly = loadContextUsage(deps.contextUsageMap, db, sessionId);
|
|
169194
|
+
if (fullFeatureMode) {
|
|
169195
|
+
try {
|
|
169196
|
+
const overflowState = getOverflowState(db, sessionId);
|
|
169197
|
+
if (overflowState.needsEmergencyRecovery && contextUsageEarly.percentage < 95) {
|
|
169198
|
+
sessionLog(sessionId, `transform: bumping percentage to 95% due to overflow recovery flag (was ${contextUsageEarly.percentage.toFixed(1)}%, detectedLimit=${overflowState.detectedContextLimit || "unknown"})`);
|
|
169199
|
+
contextUsageEarly = {
|
|
169200
|
+
...contextUsageEarly,
|
|
169201
|
+
percentage: 95
|
|
169202
|
+
};
|
|
169203
|
+
}
|
|
169204
|
+
} catch (error48) {
|
|
169205
|
+
sessionLog(sessionId, "transform: overflow recovery state read failed:", getErrorMessage(error48));
|
|
169206
|
+
}
|
|
169207
|
+
}
|
|
167478
169208
|
const historyBudgetTokens = resolveHistoryBudgetTokens(deps.historyBudgetPercentage, contextUsageEarly, deps.executeThresholdPercentage, deps.getModelKey?.(sessionId), deps.executeThresholdTokens);
|
|
167479
169209
|
const schedulerDecisionEarly = resolveSchedulerDecision(deps.scheduler, sessionMeta, contextUsageEarly, sessionId, deps.getModelKey?.(sessionId));
|
|
167480
169210
|
const isCacheBusting = deps.flushedSessions.has(sessionId);
|
|
@@ -167603,10 +169333,12 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
|
|
|
167603
169333
|
const t3 = performance.now();
|
|
167604
169334
|
const strippedStructuralNoise = stripStructuralNoise(messages);
|
|
167605
169335
|
logTransformTiming(sessionId, "stripStructuralNoise", t3, `strippedParts=${strippedStructuralNoise}`);
|
|
169336
|
+
const currentSessionModel = deps.liveModelBySession?.get(sessionId) ?? findLastAssistantModel(messages) ?? undefined;
|
|
169337
|
+
const skipTypedReasoningCleanup = modelRequiresInterleavedReasoning(currentSessionModel);
|
|
167606
169338
|
const persistedReasoningWatermark = sessionMeta?.clearedReasoningThroughTag ?? 0;
|
|
167607
169339
|
if (persistedReasoningWatermark > 0) {
|
|
167608
169340
|
const tReplay = performance.now();
|
|
167609
|
-
const replayed = replayClearedReasoning(messages, reasoningByMessage, messageTagNumbers, persistedReasoningWatermark);
|
|
169341
|
+
const replayed = replayClearedReasoning(messages, reasoningByMessage, messageTagNumbers, persistedReasoningWatermark, skipTypedReasoningCleanup);
|
|
167610
169342
|
const replayedInline = replayStrippedInlineThinking(messages, messageTagNumbers, persistedReasoningWatermark);
|
|
167611
169343
|
if (replayed > 0 || replayedInline > 0) {
|
|
167612
169344
|
sessionLog(sessionId, `reasoning replay: cleared=${replayed} inlineStripped=${replayedInline} (watermark=${persistedReasoningWatermark})`);
|
|
@@ -167614,10 +169346,10 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
|
|
|
167614
169346
|
logTransformTiming(sessionId, "replayReasoningClearing", tReplay);
|
|
167615
169347
|
}
|
|
167616
169348
|
const t4 = performance.now();
|
|
167617
|
-
const strippedClearedReasoning = stripClearedReasoning(messages);
|
|
169349
|
+
const strippedClearedReasoning = stripClearedReasoning(messages, skipTypedReasoningCleanup);
|
|
167618
169350
|
logTransformTiming(sessionId, "stripClearedReasoning", t4, `strippedParts=${strippedClearedReasoning}`);
|
|
167619
169351
|
const tMergeStrip = performance.now();
|
|
167620
|
-
const strippedMergedReasoning = stripReasoningFromMergedAssistants(messages);
|
|
169352
|
+
const strippedMergedReasoning = skipTypedReasoningCleanup ? 0 : stripReasoningFromMergedAssistants(messages);
|
|
167621
169353
|
if (strippedMergedReasoning > 0) {
|
|
167622
169354
|
sessionLog(sessionId, `stripped ${strippedMergedReasoning} reasoning parts from merged assistants (anthropic groupIntoBlocks workaround)`);
|
|
167623
169355
|
}
|
|
@@ -167667,7 +169399,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
|
|
|
167667
169399
|
sessionMeta = { ...sessionMeta, compartmentInProgress };
|
|
167668
169400
|
logTransformTiming(sessionId, "compartmentPhase", tCompartmentPhase);
|
|
167669
169401
|
const tPostProcess = performance.now();
|
|
167670
|
-
runPostTransformPhase({
|
|
169402
|
+
await runPostTransformPhase({
|
|
167671
169403
|
sessionId,
|
|
167672
169404
|
db,
|
|
167673
169405
|
messages,
|
|
@@ -167697,7 +169429,9 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
|
|
|
167697
169429
|
watermark,
|
|
167698
169430
|
forceMaterializationPercentage: FORCE_MATERIALIZE_PERCENTAGE,
|
|
167699
169431
|
hasRecentReduceCall,
|
|
167700
|
-
|
|
169432
|
+
skipTypedReasoningCleanup,
|
|
169433
|
+
projectPath: deps.projectPath,
|
|
169434
|
+
autoSearch: deps.autoSearch
|
|
167701
169435
|
});
|
|
167702
169436
|
logTransformTiming(sessionId, "postTransformPhase", tPostProcess);
|
|
167703
169437
|
const msgTokens = getMessageTokensCache(sessionId);
|
|
@@ -167917,6 +169651,25 @@ function createEventHandler2(deps) {
|
|
|
167917
169651
|
}
|
|
167918
169652
|
return;
|
|
167919
169653
|
}
|
|
169654
|
+
if (input.event.type === "session.error") {
|
|
169655
|
+
const errInfo = getSessionErrorInfo(input.event.properties);
|
|
169656
|
+
if (!errInfo) {
|
|
169657
|
+
return;
|
|
169658
|
+
}
|
|
169659
|
+
try {
|
|
169660
|
+
const detection = detectOverflow(errInfo.error);
|
|
169661
|
+
if (!detection.isOverflow) {
|
|
169662
|
+
return;
|
|
169663
|
+
}
|
|
169664
|
+
const existing = getOverflowState(deps.db, errInfo.sessionID);
|
|
169665
|
+
recordOverflowDetected(deps.db, errInfo.sessionID, detection.reportedLimit);
|
|
169666
|
+
sessionLog(errInfo.sessionID, `overflow detected via session.error: reportedLimit=${detection.reportedLimit ?? "unknown"} pattern=${detection.matchedPattern ?? "n/a"} (previousRecovery=${existing.needsEmergencyRecovery})`);
|
|
169667
|
+
deps.onSessionCacheInvalidated?.(errInfo.sessionID);
|
|
169668
|
+
} catch (error48) {
|
|
169669
|
+
sessionLog(errInfo.sessionID, "event session.error handling failed:", error48);
|
|
169670
|
+
}
|
|
169671
|
+
return;
|
|
169672
|
+
}
|
|
167920
169673
|
if (input.event.type === "message.updated") {
|
|
167921
169674
|
const info = getMessageUpdatedAssistantInfo(input.event.properties);
|
|
167922
169675
|
if (!info) {
|
|
@@ -167933,6 +169686,18 @@ function createEventHandler2(deps) {
|
|
|
167933
169686
|
} else {
|
|
167934
169687
|
clearMessageTokensCache(info.sessionID);
|
|
167935
169688
|
}
|
|
169689
|
+
if (info.error !== undefined && info.error !== null) {
|
|
169690
|
+
const detection = detectOverflow(info.error);
|
|
169691
|
+
if (detection.isOverflow) {
|
|
169692
|
+
try {
|
|
169693
|
+
recordOverflowDetected(deps.db, info.sessionID, detection.reportedLimit);
|
|
169694
|
+
sessionLog(info.sessionID, `overflow detected via message.updated: reportedLimit=${detection.reportedLimit ?? "unknown"} pattern=${detection.matchedPattern ?? "n/a"}`);
|
|
169695
|
+
deps.onSessionCacheInvalidated?.(info.sessionID);
|
|
169696
|
+
} catch (error48) {
|
|
169697
|
+
sessionLog(info.sessionID, "event message.updated overflow persistence failed:", error48);
|
|
169698
|
+
}
|
|
169699
|
+
}
|
|
169700
|
+
}
|
|
167936
169701
|
const now = Date.now();
|
|
167937
169702
|
const usageTokens = [
|
|
167938
169703
|
info.tokens?.input,
|
|
@@ -167958,7 +169723,10 @@ function createEventHandler2(deps) {
|
|
|
167958
169723
|
}
|
|
167959
169724
|
if (hasUsageTokens) {
|
|
167960
169725
|
const totalInputTokens = (info.tokens?.input ?? 0) + (info.tokens?.cache?.read ?? 0) + (info.tokens?.cache?.write ?? 0);
|
|
167961
|
-
const contextLimit = resolveContextLimit(info.providerID, info.modelID
|
|
169726
|
+
const contextLimit = resolveContextLimit(info.providerID, info.modelID, {
|
|
169727
|
+
db: deps.db,
|
|
169728
|
+
sessionID: info.sessionID
|
|
169729
|
+
});
|
|
167962
169730
|
const percentage = contextLimit > 0 ? totalInputTokens / contextLimit * 100 : 0;
|
|
167963
169731
|
sessionLog(info.sessionID, `event message.updated: totalInputTokens=${totalInputTokens} contextLimit=${contextLimit} percentage=${percentage.toFixed(1)}%`);
|
|
167964
169732
|
deps.contextUsageMap.set(info.sessionID, {
|
|
@@ -168295,6 +170063,12 @@ function createChatMessageHook(args) {
|
|
|
168295
170063
|
const sessionId = input.sessionID;
|
|
168296
170064
|
if (!sessionId)
|
|
168297
170065
|
return;
|
|
170066
|
+
if (input.model?.providerID && input.model.modelID) {
|
|
170067
|
+
args.liveModelBySession.set(sessionId, {
|
|
170068
|
+
providerID: input.model.providerID,
|
|
170069
|
+
modelID: input.model.modelID
|
|
170070
|
+
});
|
|
170071
|
+
}
|
|
168298
170072
|
if (args.ctxReduceEnabled !== false) {
|
|
168299
170073
|
const sessionMeta = getOrCreateSessionMeta(args.db, sessionId);
|
|
168300
170074
|
const turnUsage = args.toolUsageSinceUserTurn.get(sessionId);
|
|
@@ -168328,8 +170102,12 @@ function createEventHook(args) {
|
|
|
168328
170102
|
modelID: assistantInfo.modelID
|
|
168329
170103
|
});
|
|
168330
170104
|
if (previous && (previous.providerID !== assistantInfo.providerID || previous.modelID !== assistantInfo.modelID)) {
|
|
168331
|
-
sessionLog(assistantInfo.sessionID, `model changed (${previous.providerID}/${previous.modelID} -> ${assistantInfo.providerID}/${assistantInfo.modelID}), clearing historian failure state`);
|
|
170105
|
+
sessionLog(assistantInfo.sessionID, `model changed (${previous.providerID}/${previous.modelID} -> ${assistantInfo.providerID}/${assistantInfo.modelID}), clearing historian failure state and reasoning watermark`);
|
|
168332
170106
|
clearHistorianFailureState(args.db, assistantInfo.sessionID);
|
|
170107
|
+
clearPersistedReasoningWatermark(args.db, assistantInfo.sessionID);
|
|
170108
|
+
updateSessionMeta(args.db, assistantInfo.sessionID, {
|
|
170109
|
+
clearedReasoningThroughTag: 0
|
|
170110
|
+
});
|
|
168333
170111
|
}
|
|
168334
170112
|
}
|
|
168335
170113
|
}
|
|
@@ -168348,6 +170126,7 @@ function createEventHook(args) {
|
|
|
168348
170126
|
args.lastHeuristicsTurnId.delete(sessionId);
|
|
168349
170127
|
args.commitSeenLastPass?.delete(sessionId);
|
|
168350
170128
|
clearNoteNudgeState(args.db, sessionId);
|
|
170129
|
+
clearAutoSearchForSession(sessionId);
|
|
168351
170130
|
}
|
|
168352
170131
|
if (input.event.type === "message.removed") {
|
|
168353
170132
|
return;
|
|
@@ -168958,13 +170737,21 @@ function createMagicContextHook(deps) {
|
|
|
168958
170737
|
},
|
|
168959
170738
|
projectPath,
|
|
168960
170739
|
experimentalCompactionMarkers: deps.config.compaction_markers,
|
|
168961
|
-
experimentalUserMemories: deps.config.
|
|
170740
|
+
experimentalUserMemories: deps.config.dreamer?.user_memories?.enabled,
|
|
168962
170741
|
experimentalTemporalAwareness: deps.config.experimental?.temporal_awareness === true,
|
|
168963
170742
|
historianTwoPass: deps.config.historian?.two_pass === true,
|
|
168964
170743
|
compressorMinCompartmentRatio: deps.config.compressor?.enabled === false ? undefined : deps.config.compressor?.min_compartment_ratio,
|
|
168965
170744
|
compressorMaxMergeDepth: deps.config.compressor?.enabled === false ? undefined : deps.config.compressor?.max_merge_depth,
|
|
168966
170745
|
compressorCooldownMs: deps.config.compressor?.enabled === false ? undefined : deps.config.compressor?.cooldown_ms,
|
|
168967
|
-
liveModelBySession
|
|
170746
|
+
liveModelBySession,
|
|
170747
|
+
autoSearch: deps.config.experimental?.auto_search?.enabled ? {
|
|
170748
|
+
enabled: true,
|
|
170749
|
+
scoreThreshold: deps.config.experimental.auto_search.score_threshold,
|
|
170750
|
+
minPromptChars: deps.config.experimental.auto_search.min_prompt_chars,
|
|
170751
|
+
memoryEnabled: deps.config.memory?.enabled !== false,
|
|
170752
|
+
embeddingEnabled: deps.config.embedding?.provider !== "off",
|
|
170753
|
+
gitCommitsEnabled: deps.config.experimental?.git_commit_indexing?.enabled === true
|
|
170754
|
+
} : undefined
|
|
168968
170755
|
});
|
|
168969
170756
|
const eventHandler = createEventHandler2({
|
|
168970
170757
|
contextUsageMap,
|
|
@@ -169000,14 +170787,14 @@ function createMagicContextHook(deps) {
|
|
|
169000
170787
|
tasks: dreaming.tasks,
|
|
169001
170788
|
taskTimeoutMinutes: dreaming.task_timeout_minutes,
|
|
169002
170789
|
maxRuntimeMinutes: dreaming.max_runtime_minutes,
|
|
169003
|
-
experimentalUserMemories: deps.config.
|
|
170790
|
+
experimentalUserMemories: deps.config.dreamer?.user_memories?.enabled ? {
|
|
169004
170791
|
enabled: true,
|
|
169005
|
-
promotionThreshold: deps.config.
|
|
170792
|
+
promotionThreshold: deps.config.dreamer.user_memories.promotion_threshold
|
|
169006
170793
|
} : undefined,
|
|
169007
|
-
experimentalPinKeyFiles: deps.config.
|
|
170794
|
+
experimentalPinKeyFiles: deps.config.dreamer?.pin_key_files?.enabled ? {
|
|
169008
170795
|
enabled: true,
|
|
169009
|
-
token_budget: deps.config.
|
|
169010
|
-
min_reads: deps.config.
|
|
170796
|
+
token_budget: deps.config.dreamer.pin_key_files.token_budget,
|
|
170797
|
+
min_reads: deps.config.dreamer.pin_key_files.min_reads
|
|
169011
170798
|
} : undefined
|
|
169012
170799
|
}).catch((error48) => {
|
|
169013
170800
|
log("[dreamer] scheduled queue processing failed:", error48);
|
|
@@ -169063,14 +170850,14 @@ function createMagicContextHook(deps) {
|
|
|
169063
170850
|
projectPath,
|
|
169064
170851
|
client: deps.client,
|
|
169065
170852
|
directory: deps.directory,
|
|
169066
|
-
experimentalUserMemories: deps.config.
|
|
170853
|
+
experimentalUserMemories: deps.config.dreamer?.user_memories?.enabled ? {
|
|
169067
170854
|
enabled: true,
|
|
169068
|
-
promotionThreshold: deps.config.
|
|
170855
|
+
promotionThreshold: deps.config.dreamer.user_memories.promotion_threshold
|
|
169069
170856
|
} : undefined,
|
|
169070
|
-
experimentalPinKeyFiles: deps.config.
|
|
170857
|
+
experimentalPinKeyFiles: deps.config.dreamer?.pin_key_files?.enabled ? {
|
|
169071
170858
|
enabled: true,
|
|
169072
|
-
token_budget: deps.config.
|
|
169073
|
-
min_reads: deps.config.
|
|
170859
|
+
token_budget: deps.config.dreamer.pin_key_files.token_budget,
|
|
170860
|
+
min_reads: deps.config.dreamer.pin_key_files.min_reads
|
|
169074
170861
|
} : undefined
|
|
169075
170862
|
} : undefined
|
|
169076
170863
|
});
|
|
@@ -169085,9 +170872,9 @@ function createMagicContextHook(deps) {
|
|
|
169085
170872
|
directory: deps.directory,
|
|
169086
170873
|
flushedSessions,
|
|
169087
170874
|
lastHeuristicsTurnId,
|
|
169088
|
-
experimentalUserMemories: deps.config.
|
|
169089
|
-
experimentalPinKeyFiles: deps.config.
|
|
169090
|
-
experimentalPinKeyFilesTokenBudget: deps.config.
|
|
170875
|
+
experimentalUserMemories: deps.config.dreamer?.user_memories?.enabled,
|
|
170876
|
+
experimentalPinKeyFiles: deps.config.dreamer?.pin_key_files?.enabled ?? false,
|
|
170877
|
+
experimentalPinKeyFilesTokenBudget: deps.config.dreamer?.pin_key_files?.token_budget,
|
|
169091
170878
|
experimentalTemporalAwareness: deps.config.experimental?.temporal_awareness === true
|
|
169092
170879
|
});
|
|
169093
170880
|
const eventHook = createEventHook({
|
|
@@ -169115,6 +170902,7 @@ function createMagicContextHook(deps) {
|
|
|
169115
170902
|
db,
|
|
169116
170903
|
toolUsageSinceUserTurn,
|
|
169117
170904
|
recentReduceBySession,
|
|
170905
|
+
liveModelBySession,
|
|
169118
170906
|
variantBySession,
|
|
169119
170907
|
agentBySession,
|
|
169120
170908
|
flushedSessions,
|
|
@@ -169172,6 +170960,8 @@ function createSessionHooks(args) {
|
|
|
169172
170960
|
memory: pluginConfig.memory,
|
|
169173
170961
|
sidekick: pluginConfig.sidekick,
|
|
169174
170962
|
dreamer: pluginConfig.dreamer,
|
|
170963
|
+
commit_cluster_trigger: pluginConfig.commit_cluster_trigger,
|
|
170964
|
+
compaction_markers: pluginConfig.compaction_markers,
|
|
169175
170965
|
compressor: pluginConfig.compressor,
|
|
169176
170966
|
experimental: pluginConfig.experimental
|
|
169177
170967
|
}
|
|
@@ -169666,7 +171456,7 @@ var MEMORY_CATEGORIES = new Set(CATEGORY_PRIORITY);
|
|
|
169666
171456
|
function isMemoryCategory2(value) {
|
|
169667
171457
|
return MEMORY_CATEGORIES.has(value);
|
|
169668
171458
|
}
|
|
169669
|
-
function
|
|
171459
|
+
function normalizeLimit2(limit) {
|
|
169670
171460
|
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
169671
171461
|
return DEFAULT_SEARCH_LIMIT2;
|
|
169672
171462
|
}
|
|
@@ -169841,7 +171631,7 @@ function createCtxMemoryTool(deps) {
|
|
|
169841
171631
|
return `Archived memory [ID: ${args.id}].`;
|
|
169842
171632
|
}
|
|
169843
171633
|
if (args.action === "list") {
|
|
169844
|
-
const limit =
|
|
171634
|
+
const limit = normalizeLimit2(args.limit);
|
|
169845
171635
|
const category = normalizeCategory(args.category);
|
|
169846
171636
|
const memories = filterByCategory(getMemoriesByProject(deps.db, deps.projectPath), category).slice(0, limit);
|
|
169847
171637
|
return formatMemoryList(memories);
|
|
@@ -170130,6 +171920,9 @@ ${parts.join(`
|
|
|
170130
171920
|
projectIdentity: deps.projectIdentity,
|
|
170131
171921
|
sessionId
|
|
170132
171922
|
});
|
|
171923
|
+
try {
|
|
171924
|
+
setNoteLastReadAt(deps.db, sessionId);
|
|
171925
|
+
} catch {}
|
|
170133
171926
|
if (sections.length === 0) {
|
|
170134
171927
|
return `## Notes
|
|
170135
171928
|
|
|
@@ -170288,336 +172081,73 @@ function createCtxReduceTools(deps) {
|
|
|
170288
172081
|
}
|
|
170289
172082
|
// src/tools/ctx-search/constants.ts
|
|
170290
172083
|
var CTX_SEARCH_TOOL_NAME = "ctx_search";
|
|
170291
|
-
var CTX_SEARCH_DESCRIPTION =
|
|
172084
|
+
var CTX_SEARCH_DESCRIPTION = [
|
|
172085
|
+
"Search across project memories, indexed git commits, and raw conversation history.",
|
|
172086
|
+
"",
|
|
172087
|
+
"Sources:",
|
|
172088
|
+
"- memory: curated cross-session knowledge (highest-signal; already-visible ones are filtered out)",
|
|
172089
|
+
"- message: raw user/assistant messages from older compartmentalized history",
|
|
172090
|
+
"- git_commit: HEAD git commits (enabled when experimental.git_commit_indexing is on)",
|
|
172091
|
+
"",
|
|
172092
|
+
"Narrow via the `sources` param when the question maps to a specific channel:",
|
|
172093
|
+
'- "was this working before / when did this break" \u2192 ["git_commit", "message"]',
|
|
172094
|
+
'- "when did we change this" \u2192 ["git_commit"]',
|
|
172095
|
+
'- "what is our naming convention" \u2192 ["memory"]',
|
|
172096
|
+
'- "did we discuss this earlier" \u2192 ["message"]',
|
|
172097
|
+
"Omit sources for a broad search across all enabled channels.",
|
|
172098
|
+
"",
|
|
172099
|
+
"Memories already rendered in <session-history> are hard-filtered from results \u2014 the agent already sees them in context.",
|
|
172100
|
+
"Session facts are NOT a search source; they're always visible in <session-history>.",
|
|
172101
|
+
"Message results include ordinals the agent can pass to ctx_expand for full context."
|
|
172102
|
+
].join(`
|
|
172103
|
+
`);
|
|
170292
172104
|
var DEFAULT_CTX_SEARCH_LIMIT = 10;
|
|
170293
172105
|
// src/tools/ctx-search/tools.ts
|
|
170294
172106
|
init_compartment_storage();
|
|
170295
172107
|
import { tool as tool5 } from "@opencode-ai/plugin";
|
|
170296
|
-
|
|
170297
|
-
|
|
170298
|
-
init_read_session_chunk();
|
|
170299
|
-
init_logger();
|
|
170300
|
-
init_memory();
|
|
170301
|
-
init_embedding();
|
|
170302
|
-
init_storage_memory_fts();
|
|
170303
|
-
init_message_index();
|
|
170304
|
-
init_storage();
|
|
170305
|
-
var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
|
|
170306
|
-
var FTS_SEMANTIC_CANDIDATE_LIMIT = 50;
|
|
170307
|
-
var SEMANTIC_WEIGHT = 0.7;
|
|
170308
|
-
var FTS_WEIGHT = 0.3;
|
|
170309
|
-
var SINGLE_SOURCE_PENALTY = 0.8;
|
|
170310
|
-
var RESULT_PREVIEW_LIMIT = 220;
|
|
170311
|
-
var MEMORY_SOURCE_BOOST = 1.3;
|
|
170312
|
-
var FACT_SOURCE_BOOST = 1.15;
|
|
170313
|
-
var MESSAGE_SOURCE_BOOST = 1;
|
|
170314
|
-
var messageSearchStatements = new WeakMap;
|
|
170315
|
-
function normalizeLimit2(limit) {
|
|
170316
|
-
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
170317
|
-
return DEFAULT_UNIFIED_SEARCH_LIMIT;
|
|
170318
|
-
}
|
|
170319
|
-
return Math.max(1, Math.floor(limit));
|
|
170320
|
-
}
|
|
170321
|
-
function normalizeCosineScore(score) {
|
|
170322
|
-
if (!Number.isFinite(score)) {
|
|
170323
|
-
return 0;
|
|
170324
|
-
}
|
|
170325
|
-
return Math.min(1, Math.max(0, score));
|
|
170326
|
-
}
|
|
170327
|
-
function previewText(text) {
|
|
170328
|
-
const normalized = text.replace(/\s+/g, " ").trim();
|
|
170329
|
-
if (normalized.length <= RESULT_PREVIEW_LIMIT) {
|
|
170330
|
-
return normalized;
|
|
170331
|
-
}
|
|
170332
|
-
return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}\u2026`;
|
|
170333
|
-
}
|
|
170334
|
-
function tokenizeQuery(query) {
|
|
170335
|
-
return Array.from(new Set(query.toLowerCase().split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0)));
|
|
170336
|
-
}
|
|
170337
|
-
function scoreTextMatch(content, query, extraText = "") {
|
|
170338
|
-
const tokens = tokenizeQuery(query);
|
|
170339
|
-
if (tokens.length === 0) {
|
|
170340
|
-
return 0;
|
|
170341
|
-
}
|
|
170342
|
-
const haystack = `${content} ${extraText}`.toLowerCase();
|
|
170343
|
-
const queryLower = query.trim().toLowerCase();
|
|
170344
|
-
let matchedTokens = 0;
|
|
170345
|
-
for (const token of tokens) {
|
|
170346
|
-
if (haystack.includes(token)) {
|
|
170347
|
-
matchedTokens++;
|
|
170348
|
-
}
|
|
170349
|
-
}
|
|
170350
|
-
if (matchedTokens === 0) {
|
|
170351
|
-
return 0;
|
|
170352
|
-
}
|
|
170353
|
-
let score = matchedTokens / tokens.length;
|
|
170354
|
-
if (queryLower.length > 0 && haystack.includes(queryLower)) {
|
|
170355
|
-
score += 0.35;
|
|
170356
|
-
}
|
|
170357
|
-
return Math.min(1, score);
|
|
170358
|
-
}
|
|
170359
|
-
function getMessageSearchStatement(db) {
|
|
170360
|
-
let stmt = messageSearchStatements.get(db);
|
|
170361
|
-
if (!stmt) {
|
|
170362
|
-
stmt = db.prepare("SELECT message_ordinal AS messageOrdinal, message_id AS messageId, role, content FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ? ORDER BY bm25(message_history_fts), CAST(message_ordinal AS INTEGER) ASC LIMIT ?");
|
|
170363
|
-
messageSearchStatements.set(db, stmt);
|
|
170364
|
-
}
|
|
170365
|
-
return stmt;
|
|
170366
|
-
}
|
|
170367
|
-
function getMessageOrdinal(value) {
|
|
170368
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
170369
|
-
return value;
|
|
170370
|
-
}
|
|
170371
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
170372
|
-
const parsed = Number.parseInt(value, 10);
|
|
170373
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
170374
|
-
}
|
|
170375
|
-
return null;
|
|
170376
|
-
}
|
|
170377
|
-
async function getSemanticScores(args) {
|
|
170378
|
-
const semanticScores = new Map;
|
|
170379
|
-
if (!args.embeddingEnabled || !args.isEmbeddingRuntimeEnabled() || args.memories.length === 0) {
|
|
170380
|
-
return semanticScores;
|
|
170381
|
-
}
|
|
170382
|
-
const queryEmbedding = await args.embedQuery(args.query);
|
|
170383
|
-
if (!queryEmbedding) {
|
|
170384
|
-
return semanticScores;
|
|
170385
|
-
}
|
|
170386
|
-
const cachedEmbeddings = getProjectEmbeddings(args.db, args.projectPath);
|
|
170387
|
-
const embeddings = await ensureMemoryEmbeddings({
|
|
170388
|
-
db: args.db,
|
|
170389
|
-
memories: args.memories,
|
|
170390
|
-
existingEmbeddings: cachedEmbeddings
|
|
170391
|
-
});
|
|
170392
|
-
for (const memory of args.memories) {
|
|
170393
|
-
const memoryEmbedding = embeddings.get(memory.id);
|
|
170394
|
-
if (!memoryEmbedding) {
|
|
170395
|
-
continue;
|
|
170396
|
-
}
|
|
170397
|
-
semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(queryEmbedding, memoryEmbedding)));
|
|
170398
|
-
}
|
|
170399
|
-
return semanticScores;
|
|
170400
|
-
}
|
|
170401
|
-
function getFtsMatches(args) {
|
|
170402
|
-
try {
|
|
170403
|
-
return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
|
|
170404
|
-
} catch (error48) {
|
|
170405
|
-
log(`[search] FTS query failed for "${args.query}": ${error48 instanceof Error ? error48.message : String(error48)}`);
|
|
170406
|
-
return [];
|
|
170407
|
-
}
|
|
170408
|
-
}
|
|
170409
|
-
function getFtsScores(matches) {
|
|
170410
|
-
return new Map(matches.map((memory, rank) => [memory.id, 1 / (rank + 1)]));
|
|
170411
|
-
}
|
|
170412
|
-
function selectSemanticCandidates(args) {
|
|
170413
|
-
if (args.ftsMatches.length === 0) {
|
|
170414
|
-
return args.memories;
|
|
170415
|
-
}
|
|
170416
|
-
const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
|
|
170417
|
-
const cachedEmbeddings = peekProjectEmbeddings(args.projectPath);
|
|
170418
|
-
if (cachedEmbeddings) {
|
|
170419
|
-
for (const memoryId of cachedEmbeddings.keys()) {
|
|
170420
|
-
candidateIds.add(memoryId);
|
|
170421
|
-
}
|
|
170422
|
-
}
|
|
170423
|
-
return args.memories.filter((memory) => candidateIds.has(memory.id));
|
|
170424
|
-
}
|
|
170425
|
-
function mergeMemoryResults(args) {
|
|
170426
|
-
const memoryById = new Map(args.memories.map((memory) => [memory.id, memory]));
|
|
170427
|
-
const candidateIds = new Set([...args.semanticScores.keys(), ...args.ftsScores.keys()]);
|
|
170428
|
-
const results = [];
|
|
170429
|
-
for (const id of candidateIds) {
|
|
170430
|
-
const memory = memoryById.get(id);
|
|
170431
|
-
if (!memory) {
|
|
170432
|
-
continue;
|
|
170433
|
-
}
|
|
170434
|
-
const semanticScore = args.semanticScores.get(id);
|
|
170435
|
-
const ftsScore = args.ftsScores.get(id);
|
|
170436
|
-
let score = 0;
|
|
170437
|
-
let matchType = "fts";
|
|
170438
|
-
if (semanticScore !== undefined && ftsScore !== undefined) {
|
|
170439
|
-
score = SEMANTIC_WEIGHT * semanticScore + FTS_WEIGHT * ftsScore;
|
|
170440
|
-
matchType = "hybrid";
|
|
170441
|
-
} else if (semanticScore !== undefined) {
|
|
170442
|
-
score = semanticScore * SINGLE_SOURCE_PENALTY;
|
|
170443
|
-
matchType = "semantic";
|
|
170444
|
-
} else if (ftsScore !== undefined) {
|
|
170445
|
-
score = ftsScore * SINGLE_SOURCE_PENALTY;
|
|
170446
|
-
matchType = "fts";
|
|
170447
|
-
}
|
|
170448
|
-
if (score <= 0) {
|
|
170449
|
-
continue;
|
|
170450
|
-
}
|
|
170451
|
-
results.push({
|
|
170452
|
-
source: "memory",
|
|
170453
|
-
content: previewText(memory.content),
|
|
170454
|
-
score,
|
|
170455
|
-
memoryId: memory.id,
|
|
170456
|
-
category: memory.category,
|
|
170457
|
-
matchType
|
|
170458
|
-
});
|
|
170459
|
-
}
|
|
170460
|
-
return results.sort((left, right) => {
|
|
170461
|
-
if (right.score !== left.score) {
|
|
170462
|
-
return right.score - left.score;
|
|
170463
|
-
}
|
|
170464
|
-
return left.memoryId - right.memoryId;
|
|
170465
|
-
}).slice(0, args.limit);
|
|
170466
|
-
}
|
|
170467
|
-
async function searchMemories(args) {
|
|
170468
|
-
if (!args.memoryEnabled) {
|
|
170469
|
-
return [];
|
|
170470
|
-
}
|
|
170471
|
-
const memories = getMemoriesByProject(args.db, args.projectPath);
|
|
170472
|
-
if (memories.length === 0) {
|
|
170473
|
-
return [];
|
|
170474
|
-
}
|
|
170475
|
-
const ftsMatches = getFtsMatches({
|
|
170476
|
-
db: args.db,
|
|
170477
|
-
projectPath: args.projectPath,
|
|
170478
|
-
query: args.query,
|
|
170479
|
-
limit: FTS_SEMANTIC_CANDIDATE_LIMIT
|
|
170480
|
-
});
|
|
170481
|
-
const ftsScores = getFtsScores(ftsMatches);
|
|
170482
|
-
const semanticCandidates = selectSemanticCandidates({
|
|
170483
|
-
memories,
|
|
170484
|
-
projectPath: args.projectPath,
|
|
170485
|
-
ftsMatches
|
|
170486
|
-
});
|
|
170487
|
-
const semanticScores = await getSemanticScores({
|
|
170488
|
-
db: args.db,
|
|
170489
|
-
projectPath: args.projectPath,
|
|
170490
|
-
query: args.query,
|
|
170491
|
-
memories: semanticCandidates,
|
|
170492
|
-
embeddingEnabled: args.embeddingEnabled,
|
|
170493
|
-
embedQuery: args.embedQuery,
|
|
170494
|
-
isEmbeddingRuntimeEnabled: args.isEmbeddingRuntimeEnabled
|
|
170495
|
-
});
|
|
170496
|
-
return mergeMemoryResults({
|
|
170497
|
-
memories,
|
|
170498
|
-
semanticScores,
|
|
170499
|
-
ftsScores,
|
|
170500
|
-
limit: args.limit
|
|
170501
|
-
});
|
|
170502
|
-
}
|
|
170503
|
-
function searchFacts(args) {
|
|
170504
|
-
return getSessionFacts(args.db, args.sessionId).map((fact) => ({
|
|
170505
|
-
fact,
|
|
170506
|
-
score: scoreTextMatch(fact.content, args.query, fact.category)
|
|
170507
|
-
})).filter((candidate) => candidate.score > 0).sort((left, right) => {
|
|
170508
|
-
if (right.score !== left.score) {
|
|
170509
|
-
return right.score - left.score;
|
|
170510
|
-
}
|
|
170511
|
-
return left.fact.id - right.fact.id;
|
|
170512
|
-
}).slice(0, args.limit).map(({ fact, score }) => ({
|
|
170513
|
-
source: "fact",
|
|
170514
|
-
content: previewText(fact.content),
|
|
170515
|
-
score,
|
|
170516
|
-
factId: fact.id,
|
|
170517
|
-
factCategory: fact.category
|
|
170518
|
-
}));
|
|
170519
|
-
}
|
|
170520
|
-
function searchMessages(args) {
|
|
170521
|
-
ensureMessagesIndexed(args.db, args.sessionId, args.readMessages);
|
|
170522
|
-
const sanitizedQuery = sanitizeFtsQuery(args.query.trim());
|
|
170523
|
-
if (sanitizedQuery.length === 0) {
|
|
170524
|
-
return [];
|
|
170525
|
-
}
|
|
170526
|
-
const fetchLimit = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.limit * 3 : args.limit;
|
|
170527
|
-
const rows = getMessageSearchStatement(args.db).all(args.sessionId, sanitizedQuery, fetchLimit).map((row) => row);
|
|
170528
|
-
const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
|
|
170529
|
-
return rows.map((row, rank) => {
|
|
170530
|
-
const messageOrdinal = getMessageOrdinal(row.messageOrdinal);
|
|
170531
|
-
if (messageOrdinal === null || typeof row.messageId !== "string" || typeof row.role !== "string" || typeof row.content !== "string") {
|
|
170532
|
-
return null;
|
|
170533
|
-
}
|
|
170534
|
-
if (cutoff !== null && messageOrdinal > cutoff) {
|
|
170535
|
-
return null;
|
|
170536
|
-
}
|
|
170537
|
-
return {
|
|
170538
|
-
source: "message",
|
|
170539
|
-
content: previewText(row.content),
|
|
170540
|
-
score: 1 / (rank + 1),
|
|
170541
|
-
messageOrdinal,
|
|
170542
|
-
messageId: row.messageId,
|
|
170543
|
-
role: row.role
|
|
170544
|
-
};
|
|
170545
|
-
}).filter((result) => result !== null).slice(0, args.limit);
|
|
170546
|
-
}
|
|
170547
|
-
function getSourceBoost(result) {
|
|
170548
|
-
switch (result.source) {
|
|
170549
|
-
case "memory":
|
|
170550
|
-
return MEMORY_SOURCE_BOOST;
|
|
170551
|
-
case "fact":
|
|
170552
|
-
return FACT_SOURCE_BOOST;
|
|
170553
|
-
case "message":
|
|
170554
|
-
return MESSAGE_SOURCE_BOOST;
|
|
170555
|
-
}
|
|
170556
|
-
}
|
|
170557
|
-
function compareUnifiedResults(left, right) {
|
|
170558
|
-
const leftEffective = left.score * getSourceBoost(left);
|
|
170559
|
-
const rightEffective = right.score * getSourceBoost(right);
|
|
170560
|
-
if (rightEffective !== leftEffective) {
|
|
170561
|
-
return rightEffective - leftEffective;
|
|
170562
|
-
}
|
|
170563
|
-
if (left.source === "memory" && right.source === "memory") {
|
|
170564
|
-
return left.memoryId - right.memoryId;
|
|
170565
|
-
}
|
|
170566
|
-
if (left.source === "fact" && right.source === "fact") {
|
|
170567
|
-
return left.factId - right.factId;
|
|
170568
|
-
}
|
|
170569
|
-
if (left.source === "message" && right.source === "message") {
|
|
170570
|
-
return left.messageOrdinal - right.messageOrdinal;
|
|
170571
|
-
}
|
|
170572
|
-
return 0;
|
|
170573
|
-
}
|
|
170574
|
-
async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
|
|
170575
|
-
const trimmedQuery = query.trim();
|
|
170576
|
-
if (trimmedQuery.length === 0) {
|
|
170577
|
-
return [];
|
|
170578
|
-
}
|
|
170579
|
-
const limit = normalizeLimit2(options.limit);
|
|
170580
|
-
const tierLimit = Math.max(limit * 3, DEFAULT_UNIFIED_SEARCH_LIMIT);
|
|
170581
|
-
const [memoryResults, factResults, messageResults] = await Promise.all([
|
|
170582
|
-
searchMemories({
|
|
170583
|
-
db,
|
|
170584
|
-
projectPath,
|
|
170585
|
-
query: trimmedQuery,
|
|
170586
|
-
limit: tierLimit,
|
|
170587
|
-
memoryEnabled: options.memoryEnabled ?? true,
|
|
170588
|
-
embeddingEnabled: options.embeddingEnabled ?? true,
|
|
170589
|
-
embedQuery: options.embedQuery ?? embedText,
|
|
170590
|
-
isEmbeddingRuntimeEnabled: options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled
|
|
170591
|
-
}),
|
|
170592
|
-
Promise.resolve(searchFacts({ db, sessionId, query: trimmedQuery, limit: tierLimit })),
|
|
170593
|
-
Promise.resolve(searchMessages({
|
|
170594
|
-
db,
|
|
170595
|
-
sessionId,
|
|
170596
|
-
query: trimmedQuery,
|
|
170597
|
-
limit: tierLimit,
|
|
170598
|
-
readMessages: options.readMessages ?? readRawSessionMessages,
|
|
170599
|
-
maxOrdinal: options.maxMessageOrdinal
|
|
170600
|
-
}))
|
|
170601
|
-
]);
|
|
170602
|
-
const results = [...memoryResults, ...factResults, ...messageResults].sort(compareUnifiedResults).slice(0, limit);
|
|
170603
|
-
const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
|
|
170604
|
-
if (memoryIds.length > 0) {
|
|
170605
|
-
db.transaction(() => {
|
|
170606
|
-
for (const memoryId of memoryIds) {
|
|
170607
|
-
updateMemoryRetrievalCount(db, memoryId);
|
|
170608
|
-
}
|
|
170609
|
-
})();
|
|
170610
|
-
}
|
|
170611
|
-
return results;
|
|
170612
|
-
}
|
|
170613
|
-
|
|
170614
|
-
// src/tools/ctx-search/tools.ts
|
|
172108
|
+
init_inject_compartments();
|
|
172109
|
+
var VALID_SOURCES = new Set(["memory", "message", "git_commit"]);
|
|
170615
172110
|
function normalizeLimit3(limit) {
|
|
170616
172111
|
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
170617
172112
|
return DEFAULT_CTX_SEARCH_LIMIT;
|
|
170618
172113
|
}
|
|
170619
172114
|
return Math.max(1, Math.floor(limit));
|
|
170620
172115
|
}
|
|
172116
|
+
function normalizeSources(sources) {
|
|
172117
|
+
if (!sources || sources.length === 0)
|
|
172118
|
+
return;
|
|
172119
|
+
const result = [];
|
|
172120
|
+
const seen = new Set;
|
|
172121
|
+
for (const source of sources) {
|
|
172122
|
+
if (VALID_SOURCES.has(source)) {
|
|
172123
|
+
const typed = source;
|
|
172124
|
+
if (!seen.has(typed)) {
|
|
172125
|
+
seen.add(typed);
|
|
172126
|
+
result.push(typed);
|
|
172127
|
+
}
|
|
172128
|
+
}
|
|
172129
|
+
}
|
|
172130
|
+
return result.length > 0 ? result : undefined;
|
|
172131
|
+
}
|
|
172132
|
+
function formatAge2(committedAtMs) {
|
|
172133
|
+
const ageMs = Date.now() - committedAtMs;
|
|
172134
|
+
if (ageMs < 0)
|
|
172135
|
+
return "future";
|
|
172136
|
+
const days = Math.floor(ageMs / (24 * 60 * 60 * 1000));
|
|
172137
|
+
if (days <= 0)
|
|
172138
|
+
return "today";
|
|
172139
|
+
if (days === 1)
|
|
172140
|
+
return "1d ago";
|
|
172141
|
+
if (days < 30)
|
|
172142
|
+
return `${days}d ago`;
|
|
172143
|
+
const months = Math.floor(days / 30);
|
|
172144
|
+
if (months === 1)
|
|
172145
|
+
return "1mo ago";
|
|
172146
|
+
if (months < 12)
|
|
172147
|
+
return `${months}mo ago`;
|
|
172148
|
+
const years = Math.floor(days / 365);
|
|
172149
|
+
return years === 1 ? "1y ago" : `${years}y ago`;
|
|
172150
|
+
}
|
|
170621
172151
|
function formatResult(result, index) {
|
|
170622
172152
|
if (result.source === "memory") {
|
|
170623
172153
|
return [
|
|
@@ -170626,9 +172156,9 @@ function formatResult(result, index) {
|
|
|
170626
172156
|
].join(`
|
|
170627
172157
|
`);
|
|
170628
172158
|
}
|
|
170629
|
-
if (result.source === "
|
|
172159
|
+
if (result.source === "git_commit") {
|
|
170630
172160
|
return [
|
|
170631
|
-
`[${index}] [
|
|
172161
|
+
`[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatAge2(result.committedAtMs)} match=${result.matchType}`,
|
|
170632
172162
|
result.content
|
|
170633
172163
|
].join(`
|
|
170634
172164
|
`);
|
|
@@ -170644,7 +172174,7 @@ function formatResult(result, index) {
|
|
|
170644
172174
|
}
|
|
170645
172175
|
function formatSearchResults(query, results) {
|
|
170646
172176
|
if (results.length === 0) {
|
|
170647
|
-
return `No results found for "${query}" across memories,
|
|
172177
|
+
return `No results found for "${query}" across memories, git commits, or message history.`;
|
|
170648
172178
|
}
|
|
170649
172179
|
const body = results.map((result, index) => formatResult(result, index + 1)).join(`
|
|
170650
172180
|
|
|
@@ -170657,8 +172187,9 @@ function createCtxSearchTool(deps) {
|
|
|
170657
172187
|
return tool5({
|
|
170658
172188
|
description: CTX_SEARCH_DESCRIPTION,
|
|
170659
172189
|
args: {
|
|
170660
|
-
query: tool5.schema.string().describe("Search query
|
|
170661
|
-
limit: tool5.schema.number().optional().describe("Maximum results to return (default: 10)")
|
|
172190
|
+
query: tool5.schema.string().describe("Search query. Matches against memory content, git commit messages, and raw user/assistant message text."),
|
|
172191
|
+
limit: tool5.schema.number().optional().describe("Maximum results to return (default: 10)"),
|
|
172192
|
+
sources: tool5.schema.array(tool5.schema.enum(["memory", "message", "git_commit"])).optional().describe('Optional. Restrict to specific sources. Examples: ["git_commit"] for "when did we change X", ["memory"] for naming conventions, ["message"] for "did we discuss this earlier", ["git_commit","message"] for regression hunts. Omit for a broad search across all enabled sources.')
|
|
170662
172193
|
},
|
|
170663
172194
|
async execute(args, toolContext) {
|
|
170664
172195
|
const query = args.query?.trim();
|
|
@@ -170666,12 +172197,16 @@ function createCtxSearchTool(deps) {
|
|
|
170666
172197
|
return "Error: 'query' is required.";
|
|
170667
172198
|
}
|
|
170668
172199
|
const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, toolContext.sessionID);
|
|
172200
|
+
const visibleMemoryIds = getVisibleMemoryIds(deps.db, toolContext.sessionID);
|
|
170669
172201
|
const results = await unifiedSearch(deps.db, toolContext.sessionID, deps.projectPath, query, {
|
|
170670
172202
|
limit: normalizeLimit3(args.limit),
|
|
170671
172203
|
memoryEnabled: deps.memoryEnabled,
|
|
170672
172204
|
embeddingEnabled: deps.embeddingEnabled,
|
|
170673
172205
|
readMessages: deps.readMessages,
|
|
170674
|
-
maxMessageOrdinal: lastCompartmentEnd >= 0 ? lastCompartmentEnd : undefined
|
|
172206
|
+
maxMessageOrdinal: lastCompartmentEnd >= 0 ? lastCompartmentEnd : undefined,
|
|
172207
|
+
gitCommitsEnabled: deps.gitCommitsEnabled ?? false,
|
|
172208
|
+
sources: normalizeSources(args.sources),
|
|
172209
|
+
visibleMemoryIds
|
|
170675
172210
|
});
|
|
170676
172211
|
return formatSearchResults(query, results);
|
|
170677
172212
|
}
|
|
@@ -170753,7 +172288,8 @@ function createToolRegistry(args) {
|
|
|
170753
172288
|
db,
|
|
170754
172289
|
projectPath,
|
|
170755
172290
|
memoryEnabled,
|
|
170756
|
-
embeddingEnabled: embeddingConfig2.provider !== "off"
|
|
172291
|
+
embeddingEnabled: embeddingConfig2.provider !== "off",
|
|
172292
|
+
gitCommitsEnabled: pluginConfig.experimental?.git_commit_indexing?.enabled === true
|
|
170757
172293
|
}),
|
|
170758
172294
|
...memoryEnabled ? {
|
|
170759
172295
|
...createCtxMemoryTools({
|
|
@@ -170918,8 +172454,10 @@ var plugin = async (ctx) => {
|
|
|
170918
172454
|
setTimeout(async () => {
|
|
170919
172455
|
try {
|
|
170920
172456
|
const { sendIgnoredMessage: sendIgnoredMessage2 } = await Promise.resolve().then(() => (init_send_session_notification(), exports_send_session_notification));
|
|
170921
|
-
const
|
|
170922
|
-
const
|
|
172457
|
+
const clientWithSessions = ctx.client;
|
|
172458
|
+
const sessions = await Promise.resolve(clientWithSessions.session?.list?.()).catch(() => null);
|
|
172459
|
+
const sessionList = Array.isArray(sessions) ? sessions : sessions?.data;
|
|
172460
|
+
const sessionId = sessionList?.[0]?.id;
|
|
170923
172461
|
if (sessionId) {
|
|
170924
172462
|
await sendIgnoredMessage2(ctx.client, sessionId, warningText, {});
|
|
170925
172463
|
}
|
|
@@ -170953,14 +172491,19 @@ var plugin = async (ctx) => {
|
|
|
170953
172491
|
dreamerConfig: pluginConfig.dreamer,
|
|
170954
172492
|
embeddingConfig: pluginConfig.embedding,
|
|
170955
172493
|
memoryEnabled: pluginConfig.memory?.enabled === true,
|
|
170956
|
-
experimentalUserMemories: pluginConfig.
|
|
172494
|
+
experimentalUserMemories: pluginConfig.dreamer?.user_memories?.enabled ? {
|
|
172495
|
+
enabled: true,
|
|
172496
|
+
promotionThreshold: pluginConfig.dreamer.user_memories.promotion_threshold
|
|
172497
|
+
} : undefined,
|
|
172498
|
+
experimentalPinKeyFiles: pluginConfig.dreamer?.pin_key_files?.enabled ? {
|
|
170957
172499
|
enabled: true,
|
|
170958
|
-
|
|
172500
|
+
token_budget: pluginConfig.dreamer.pin_key_files.token_budget,
|
|
172501
|
+
min_reads: pluginConfig.dreamer.pin_key_files.min_reads
|
|
170959
172502
|
} : undefined,
|
|
170960
|
-
|
|
172503
|
+
gitCommitIndexing: pluginConfig.experimental?.git_commit_indexing?.enabled ? {
|
|
170961
172504
|
enabled: true,
|
|
170962
|
-
|
|
170963
|
-
|
|
172505
|
+
since_days: pluginConfig.experimental.git_commit_indexing.since_days,
|
|
172506
|
+
max_commits: pluginConfig.experimental.git_commit_indexing.max_commits
|
|
170964
172507
|
} : undefined
|
|
170965
172508
|
});
|
|
170966
172509
|
const storageDir = `${getOpenCodeStorageDir()}/plugin/magic-context`;
|
|
@@ -171060,7 +172603,7 @@ var plugin = async (ctx) => {
|
|
|
171060
172603
|
config2.agent = {
|
|
171061
172604
|
...config2.agent ?? {},
|
|
171062
172605
|
[DREAMER_AGENT]: buildHiddenAgentConfig(DREAMER_AGENT, DREAMER_SYSTEM_PROMPT, dreamerAgentOverrides),
|
|
171063
|
-
[HISTORIAN_AGENT]: buildHiddenAgentConfig(HISTORIAN_AGENT, pluginConfig.
|
|
172606
|
+
[HISTORIAN_AGENT]: buildHiddenAgentConfig(HISTORIAN_AGENT, pluginConfig.dreamer?.user_memories?.enabled ? COMPARTMENT_AGENT_SYSTEM_PROMPT + USER_OBSERVATIONS_APPENDIX : COMPARTMENT_AGENT_SYSTEM_PROMPT, historianAgentOverrides),
|
|
171064
172607
|
[HISTORIAN_EDITOR_AGENT]: buildHiddenAgentConfig(HISTORIAN_EDITOR_AGENT, HISTORIAN_EDITOR_SYSTEM_PROMPT, historianAgentOverrides),
|
|
171065
172608
|
[SIDEKICK_AGENT]: buildHiddenAgentConfig(SIDEKICK_AGENT, SIDEKICK_SYSTEM_PROMPT, sidekickAgentOverrides)
|
|
171066
172609
|
};
|