@cortexkit/opencode-magic-context 0.6.1 → 0.7.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 +2 -0
- package/dist/config/schema/magic-context.d.ts +17 -0
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/features/magic-context/compartment-storage.d.ts +6 -0
- package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/runner.d.ts +9 -1
- package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
- package/dist/features/magic-context/key-files/identify-key-files.d.ts +39 -0
- package/dist/features/magic-context/key-files/identify-key-files.d.ts.map +1 -0
- package/dist/features/magic-context/key-files/read-stats.d.ts +20 -0
- package/dist/features/magic-context/key-files/read-stats.d.ts.map +1 -0
- package/dist/features/magic-context/key-files/storage-key-files.d.ts +25 -0
- package/dist/features/magic-context/key-files/storage-key-files.d.ts.map +1 -0
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/command-handler.d.ts +5 -0
- package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +5 -0
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-prompt-hash.d.ts +4 -0
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +525 -72
- package/dist/plugin/dream-timer.d.ts +5 -0
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
- package/dist/tools/ctx-note/tools.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/tui/data/context-db.ts +6 -6
package/dist/index.js
CHANGED
|
@@ -22202,10 +22202,16 @@ var MagicContextConfigSchema = exports_external.object({
|
|
|
22202
22202
|
user_memories: exports_external.object({
|
|
22203
22203
|
enabled: exports_external.boolean().default(false),
|
|
22204
22204
|
promotion_threshold: exports_external.number().min(2).max(20).default(3)
|
|
22205
|
-
}).default({ enabled: false, promotion_threshold: 3 })
|
|
22205
|
+
}).default({ enabled: false, promotion_threshold: 3 }),
|
|
22206
|
+
pin_key_files: exports_external.object({
|
|
22207
|
+
enabled: exports_external.boolean().default(false),
|
|
22208
|
+
token_budget: exports_external.number().min(2000).max(30000).default(1e4),
|
|
22209
|
+
min_reads: exports_external.number().min(2).max(20).default(4)
|
|
22210
|
+
}).default({ enabled: false, token_budget: 1e4, min_reads: 4 })
|
|
22206
22211
|
}).default({
|
|
22207
22212
|
compaction_markers: false,
|
|
22208
|
-
user_memories: { enabled: false, promotion_threshold: 3 }
|
|
22213
|
+
user_memories: { enabled: false, promotion_threshold: 3 },
|
|
22214
|
+
pin_key_files: { enabled: false, token_budget: 1e4, min_reads: 4 }
|
|
22209
22215
|
}),
|
|
22210
22216
|
memory: exports_external.object({
|
|
22211
22217
|
enabled: exports_external.boolean().default(true),
|
|
@@ -23152,6 +23158,11 @@ function promoteRecompStaging(db, sessionId) {
|
|
|
23152
23158
|
return { compartments: staging.compartments, facts: staging.facts };
|
|
23153
23159
|
})();
|
|
23154
23160
|
}
|
|
23161
|
+
function invalidateAllMemoryBlockCaches(db) {
|
|
23162
|
+
try {
|
|
23163
|
+
db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE memory_block_cache != ''").run();
|
|
23164
|
+
} catch {}
|
|
23165
|
+
}
|
|
23155
23166
|
function clearRecompStaging(db, sessionId) {
|
|
23156
23167
|
db.transaction(() => {
|
|
23157
23168
|
db.prepare("DELETE FROM recomp_compartments WHERE session_id = ?").run(sessionId);
|
|
@@ -23497,8 +23508,19 @@ function clearStaleEntries(db, maxAgeMs) {
|
|
|
23497
23508
|
return result.changes;
|
|
23498
23509
|
}
|
|
23499
23510
|
// src/features/magic-context/dreamer/runner.ts
|
|
23511
|
+
import { Database } from "bun:sqlite";
|
|
23500
23512
|
import { existsSync as existsSync4 } from "fs";
|
|
23501
|
-
import { join as
|
|
23513
|
+
import { join as join7 } from "path";
|
|
23514
|
+
|
|
23515
|
+
// src/shared/data-path.ts
|
|
23516
|
+
import * as os2 from "os";
|
|
23517
|
+
import * as path2 from "path";
|
|
23518
|
+
function getDataDir() {
|
|
23519
|
+
return process.env.XDG_DATA_HOME ?? path2.join(os2.homedir(), ".local", "share");
|
|
23520
|
+
}
|
|
23521
|
+
function getOpenCodeStorageDir() {
|
|
23522
|
+
return path2.join(getDataDir(), "opencode", "storage");
|
|
23523
|
+
}
|
|
23502
23524
|
|
|
23503
23525
|
// src/shared/error-message.ts
|
|
23504
23526
|
function getErrorMessage(error48) {
|
|
@@ -23508,6 +23530,189 @@ function getErrorMessage(error48) {
|
|
|
23508
23530
|
// src/features/magic-context/dreamer/runner.ts
|
|
23509
23531
|
init_logger();
|
|
23510
23532
|
|
|
23533
|
+
// src/features/magic-context/key-files/identify-key-files.ts
|
|
23534
|
+
init_logger();
|
|
23535
|
+
|
|
23536
|
+
// src/features/magic-context/key-files/read-stats.ts
|
|
23537
|
+
function getSessionReadStats(openCodeDb, sessionId, minReads) {
|
|
23538
|
+
const fullReads = openCodeDb.prepare(`
|
|
23539
|
+
WITH full_reads AS (
|
|
23540
|
+
SELECT
|
|
23541
|
+
json_extract(json_extract(data, '$.state'), '$.input.filePath') as file_path,
|
|
23542
|
+
LENGTH(json_extract(json_extract(data, '$.state'), '$.output')) as output_bytes,
|
|
23543
|
+
p.time_created,
|
|
23544
|
+
ROW_NUMBER() OVER (
|
|
23545
|
+
PARTITION BY json_extract(json_extract(data, '$.state'), '$.input.filePath')
|
|
23546
|
+
ORDER BY p.time_created DESC
|
|
23547
|
+
) as rn
|
|
23548
|
+
FROM part p
|
|
23549
|
+
WHERE p.session_id = ?
|
|
23550
|
+
AND json_extract(data, '$.type') = 'tool'
|
|
23551
|
+
AND json_extract(data, '$.tool') = 'read'
|
|
23552
|
+
AND json_extract(json_extract(data, '$.state'), '$.input.filePath') IS NOT NULL
|
|
23553
|
+
AND json_extract(json_extract(data, '$.state'), '$.input.startLine') IS NULL
|
|
23554
|
+
AND json_extract(json_extract(data, '$.state'), '$.input.start_line') IS NULL
|
|
23555
|
+
AND json_extract(json_extract(data, '$.state'), '$.input.endLine') IS NULL
|
|
23556
|
+
AND json_extract(json_extract(data, '$.state'), '$.input.end_line') IS NULL
|
|
23557
|
+
AND json_extract(json_extract(data, '$.state'), '$.input.offset') IS NULL
|
|
23558
|
+
AND json_extract(json_extract(data, '$.state'), '$.input.limit') IS NULL
|
|
23559
|
+
),
|
|
23560
|
+
file_counts AS (
|
|
23561
|
+
SELECT file_path, COUNT(*) as full_read_count
|
|
23562
|
+
FROM full_reads
|
|
23563
|
+
GROUP BY file_path
|
|
23564
|
+
HAVING full_read_count >= ?
|
|
23565
|
+
)
|
|
23566
|
+
SELECT
|
|
23567
|
+
r.file_path,
|
|
23568
|
+
fc.full_read_count,
|
|
23569
|
+
r.output_bytes as latest_read_bytes
|
|
23570
|
+
FROM full_reads r
|
|
23571
|
+
JOIN file_counts fc ON r.file_path = fc.file_path
|
|
23572
|
+
WHERE r.rn = 1
|
|
23573
|
+
ORDER BY fc.full_read_count DESC
|
|
23574
|
+
`).all(sessionId, minReads);
|
|
23575
|
+
if (fullReads.length === 0)
|
|
23576
|
+
return [];
|
|
23577
|
+
const editCounts = new Map;
|
|
23578
|
+
const editRows = openCodeDb.prepare(`
|
|
23579
|
+
SELECT
|
|
23580
|
+
json_extract(json_extract(data, '$.state'), '$.input.filePath') as file_path,
|
|
23581
|
+
COUNT(*) as edit_count
|
|
23582
|
+
FROM part p
|
|
23583
|
+
WHERE p.session_id = ?
|
|
23584
|
+
AND json_extract(data, '$.type') = 'tool'
|
|
23585
|
+
AND json_extract(data, '$.tool') IN ('edit', 'write', 'mcp_edit', 'mcp_write')
|
|
23586
|
+
AND json_extract(json_extract(data, '$.state'), '$.input.filePath') IS NOT NULL
|
|
23587
|
+
GROUP BY file_path
|
|
23588
|
+
`).all(sessionId);
|
|
23589
|
+
for (const row of editRows) {
|
|
23590
|
+
editCounts.set(row.file_path, row.edit_count);
|
|
23591
|
+
}
|
|
23592
|
+
return fullReads.map((row) => ({
|
|
23593
|
+
filePath: row.file_path,
|
|
23594
|
+
fullReadCount: row.full_read_count,
|
|
23595
|
+
spreadAcrossCompartments: 0,
|
|
23596
|
+
editCount: editCounts.get(row.file_path) ?? 0,
|
|
23597
|
+
latestReadBytes: row.latest_read_bytes ?? 0,
|
|
23598
|
+
latestReadTokens: Math.ceil((row.latest_read_bytes ?? 0) / 3.5)
|
|
23599
|
+
}));
|
|
23600
|
+
}
|
|
23601
|
+
|
|
23602
|
+
// src/features/magic-context/key-files/storage-key-files.ts
|
|
23603
|
+
init_logger();
|
|
23604
|
+
function getKeyFiles(db, sessionId) {
|
|
23605
|
+
try {
|
|
23606
|
+
const row = db.prepare("SELECT key_files FROM session_meta WHERE session_id = ?").get(sessionId);
|
|
23607
|
+
if (!row?.key_files)
|
|
23608
|
+
return [];
|
|
23609
|
+
const parsed = JSON.parse(row.key_files);
|
|
23610
|
+
if (!Array.isArray(parsed))
|
|
23611
|
+
return [];
|
|
23612
|
+
return parsed.filter((e) => typeof e === "object" && e !== null && typeof e.filePath === "string" && typeof e.tokens === "number");
|
|
23613
|
+
} catch {
|
|
23614
|
+
return [];
|
|
23615
|
+
}
|
|
23616
|
+
}
|
|
23617
|
+
function setKeyFiles(db, sessionId, files) {
|
|
23618
|
+
try {
|
|
23619
|
+
db.prepare("INSERT OR IGNORE INTO session_meta (session_id) VALUES (?)").run(sessionId);
|
|
23620
|
+
db.prepare("UPDATE session_meta SET key_files = ? WHERE session_id = ?").run(JSON.stringify(files), sessionId);
|
|
23621
|
+
} catch (error48) {
|
|
23622
|
+
sessionLog(sessionId, "failed to persist key files:", error48);
|
|
23623
|
+
}
|
|
23624
|
+
}
|
|
23625
|
+
function greedyFitFiles(rankedFiles, tokenBudget) {
|
|
23626
|
+
const selected = [];
|
|
23627
|
+
let remainingBudget = tokenBudget;
|
|
23628
|
+
for (const file2 of rankedFiles) {
|
|
23629
|
+
if (file2.tokens <= 0)
|
|
23630
|
+
continue;
|
|
23631
|
+
if (file2.tokens > remainingBudget)
|
|
23632
|
+
continue;
|
|
23633
|
+
selected.push({ filePath: file2.filePath, tokens: file2.tokens });
|
|
23634
|
+
remainingBudget -= file2.tokens;
|
|
23635
|
+
if (remainingBudget <= 0)
|
|
23636
|
+
break;
|
|
23637
|
+
}
|
|
23638
|
+
return selected;
|
|
23639
|
+
}
|
|
23640
|
+
|
|
23641
|
+
// src/features/magic-context/key-files/identify-key-files.ts
|
|
23642
|
+
var KEY_FILES_SYSTEM_PROMPT = "You are a file importance evaluator. Given read statistics about files in a coding session, identify which are core orientation files worth pinning in context. Return a JSON array.";
|
|
23643
|
+
function buildKeyFilesPrompt(candidates, tokenBudget, minReads) {
|
|
23644
|
+
const statsText = candidates.map((s) => `- **${s.filePath}** \u2014 ${s.fullReadCount} full reads, ${s.editCount} edits, ~${s.latestReadTokens} tokens`).join(`
|
|
23645
|
+
`);
|
|
23646
|
+
return `## Identify Key Files for Pinning
|
|
23647
|
+
|
|
23648
|
+
The following files were fully read ${minReads}+ times during a coding session.
|
|
23649
|
+
Identify which ones are **core orientation files** worth keeping permanently in context.
|
|
23650
|
+
|
|
23651
|
+
### Signals of a core orientation file:
|
|
23652
|
+
- Read many times across different phases of work (not clustered in one task)
|
|
23653
|
+
- Read without editing \u2014 consulted for understanding, not modification
|
|
23654
|
+
- Contains architecture, configuration, types, or key abstractions
|
|
23655
|
+
|
|
23656
|
+
### Signals of a NON-core file (exclude):
|
|
23657
|
+
- Read many times but always edited \u2014 actively working on it
|
|
23658
|
+
- Very large (>5000 tokens) \u2014 too expensive to pin
|
|
23659
|
+
- Test files, scripts, or generated files
|
|
23660
|
+
|
|
23661
|
+
### Token budget: ${tokenBudget} tokens total
|
|
23662
|
+
|
|
23663
|
+
### Files:
|
|
23664
|
+
${statsText}
|
|
23665
|
+
|
|
23666
|
+
### Output Format
|
|
23667
|
+
Return a JSON array ranked by importance (most important first):
|
|
23668
|
+
\`\`\`json
|
|
23669
|
+
[
|
|
23670
|
+
{"filePath": "src/path/to/file.ts", "tokens": 2500, "reason": "brief reason"}
|
|
23671
|
+
]
|
|
23672
|
+
\`\`\`
|
|
23673
|
+
|
|
23674
|
+
Only include files you're confident are true orientation files. Return empty array if none qualify.`;
|
|
23675
|
+
}
|
|
23676
|
+
function parseKeyFilesOutput(text) {
|
|
23677
|
+
const jsonMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/) ?? text.match(/\[[\s\S]*\]/);
|
|
23678
|
+
if (!jsonMatch)
|
|
23679
|
+
return [];
|
|
23680
|
+
try {
|
|
23681
|
+
const raw = jsonMatch[1] ?? jsonMatch[0];
|
|
23682
|
+
const parsed = JSON.parse(raw);
|
|
23683
|
+
if (!Array.isArray(parsed))
|
|
23684
|
+
return [];
|
|
23685
|
+
return parsed.filter((item) => typeof item === "object" && item !== null && typeof item.filePath === "string" && typeof item.tokens === "number").map((item) => ({ filePath: item.filePath, tokens: item.tokens }));
|
|
23686
|
+
} catch {
|
|
23687
|
+
return [];
|
|
23688
|
+
}
|
|
23689
|
+
}
|
|
23690
|
+
function getKeyFileCandidates(openCodeDb, sessionId, minReads, tokenBudget) {
|
|
23691
|
+
const stats = getSessionReadStats(openCodeDb, sessionId, minReads);
|
|
23692
|
+
const maxPerFileTokens = Math.min(tokenBudget / 2, 5000);
|
|
23693
|
+
return stats.filter((s) => s.latestReadTokens > 0 && s.latestReadTokens <= maxPerFileTokens);
|
|
23694
|
+
}
|
|
23695
|
+
function applyKeyFileResults(db, sessionId, llmRanked, tokenBudget, candidatePaths) {
|
|
23696
|
+
const filtered = candidatePaths ? llmRanked.filter((f) => candidatePaths.has(f.filePath)) : llmRanked;
|
|
23697
|
+
const selected = greedyFitFiles(filtered, tokenBudget);
|
|
23698
|
+
setKeyFiles(db, sessionId, selected);
|
|
23699
|
+
const totalTokens = selected.reduce((sum, f) => sum + f.tokens, 0);
|
|
23700
|
+
log(`[key-files][${sessionId}] pinned ${selected.length} files (${totalTokens} tokens): ${selected.map((f) => f.filePath).join(", ")}`);
|
|
23701
|
+
return { filesIdentified: selected.length, totalTokens };
|
|
23702
|
+
}
|
|
23703
|
+
function heuristicKeyFileSelection(db, sessionId, candidates, tokenBudget) {
|
|
23704
|
+
const scored = candidates.map((c) => ({
|
|
23705
|
+
filePath: c.filePath,
|
|
23706
|
+
tokens: c.latestReadTokens,
|
|
23707
|
+
score: c.fullReadCount * 2 - c.editCount * 3
|
|
23708
|
+
})).filter((c) => c.score > 0).sort((a, b) => b.score - a.score);
|
|
23709
|
+
const selected = greedyFitFiles(scored, tokenBudget);
|
|
23710
|
+
setKeyFiles(db, sessionId, selected);
|
|
23711
|
+
const totalTokens = selected.reduce((sum, f) => sum + f.tokens, 0);
|
|
23712
|
+
log(`[key-files][${sessionId}] heuristic pinned ${selected.length} files (${totalTokens} tokens)`);
|
|
23713
|
+
return { filesIdentified: selected.length, totalTokens };
|
|
23714
|
+
}
|
|
23715
|
+
|
|
23511
23716
|
// src/features/magic-context/memory/storage-memory-embeddings.ts
|
|
23512
23717
|
var saveEmbeddingStatements = new WeakMap;
|
|
23513
23718
|
var loadAllEmbeddingsStatements = new WeakMap;
|
|
@@ -24403,6 +24608,176 @@ function countNewIds(beforeIds, afterIds) {
|
|
|
24403
24608
|
}
|
|
24404
24609
|
return count;
|
|
24405
24610
|
}
|
|
24611
|
+
function getOpenCodeDbPath() {
|
|
24612
|
+
return join7(getDataDir(), "opencode", "opencode.db");
|
|
24613
|
+
}
|
|
24614
|
+
function openOpenCodeDb() {
|
|
24615
|
+
const dbPath = getOpenCodeDbPath();
|
|
24616
|
+
if (!existsSync4(dbPath)) {
|
|
24617
|
+
log(`[key-files] OpenCode DB not found at ${dbPath} \u2014 skipping`);
|
|
24618
|
+
return null;
|
|
24619
|
+
}
|
|
24620
|
+
try {
|
|
24621
|
+
const db = new Database(dbPath, { readonly: true });
|
|
24622
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
24623
|
+
return db;
|
|
24624
|
+
} catch (error48) {
|
|
24625
|
+
log(`[key-files] failed to open OpenCode DB at ${dbPath}: ${getErrorMessage(error48)}`);
|
|
24626
|
+
return null;
|
|
24627
|
+
}
|
|
24628
|
+
}
|
|
24629
|
+
function isSessionIdRow(row) {
|
|
24630
|
+
if (row === null || typeof row !== "object") {
|
|
24631
|
+
return false;
|
|
24632
|
+
}
|
|
24633
|
+
return typeof row.sessionId === "string";
|
|
24634
|
+
}
|
|
24635
|
+
function hasExplicitEmptyKeyFilesOutput(text) {
|
|
24636
|
+
return /```(?:json)?\s*\[\s*\]\s*```/s.test(text) || /^\s*\[\s*\]\s*$/s.test(text);
|
|
24637
|
+
}
|
|
24638
|
+
async function getActiveProjectSessionIds(args) {
|
|
24639
|
+
const listResponse = await args.client.session.list({
|
|
24640
|
+
query: { directory: args.sessionDirectory ?? args.projectIdentity }
|
|
24641
|
+
});
|
|
24642
|
+
const sessions = normalizeSDKResponse(listResponse, [], {
|
|
24643
|
+
preferResponseOnMissingData: true
|
|
24644
|
+
});
|
|
24645
|
+
const projectSessionIds = new Set(sessions.map((session) => typeof session?.id === "string" ? session.id : null).filter((sessionId) => Boolean(sessionId)));
|
|
24646
|
+
if (projectSessionIds.size === 0) {
|
|
24647
|
+
return [];
|
|
24648
|
+
}
|
|
24649
|
+
return args.db.prepare("SELECT session_id AS sessionId FROM session_meta WHERE is_subagent = 0 ORDER BY session_id ASC").all().filter(isSessionIdRow).map((row) => row.sessionId).filter((sessionId) => projectSessionIds.has(sessionId));
|
|
24650
|
+
}
|
|
24651
|
+
async function identifyKeyFilesForSession(args) {
|
|
24652
|
+
let openCodeDb = null;
|
|
24653
|
+
try {
|
|
24654
|
+
openCodeDb = openOpenCodeDb();
|
|
24655
|
+
if (!openCodeDb) {
|
|
24656
|
+
return;
|
|
24657
|
+
}
|
|
24658
|
+
const candidates = getKeyFileCandidates(openCodeDb, args.sessionId, args.config.min_reads, args.config.token_budget);
|
|
24659
|
+
if (candidates.length === 0) {
|
|
24660
|
+
log(`[key-files][${args.sessionId}] no candidates found \u2014 skipping`);
|
|
24661
|
+
return;
|
|
24662
|
+
}
|
|
24663
|
+
const prompt = buildKeyFilesPrompt(candidates, args.config.token_budget, args.config.min_reads);
|
|
24664
|
+
const applyHeuristicFallback = () => {
|
|
24665
|
+
heuristicKeyFileSelection(args.db, args.sessionId, candidates, args.config.token_budget);
|
|
24666
|
+
};
|
|
24667
|
+
let agentSessionId = null;
|
|
24668
|
+
const abortController = new AbortController;
|
|
24669
|
+
const leaseInterval = setInterval(() => {
|
|
24670
|
+
try {
|
|
24671
|
+
if (!renewLease(args.db, args.holderId)) {
|
|
24672
|
+
log(`[key-files][${args.sessionId}] lease renewal failed \u2014 aborting`);
|
|
24673
|
+
abortController.abort();
|
|
24674
|
+
}
|
|
24675
|
+
} catch {
|
|
24676
|
+
abortController.abort();
|
|
24677
|
+
}
|
|
24678
|
+
}, 60000);
|
|
24679
|
+
try {
|
|
24680
|
+
const createResponse = await args.client.session.create({
|
|
24681
|
+
body: {
|
|
24682
|
+
...args.parentSessionId ? { parentID: args.parentSessionId } : {},
|
|
24683
|
+
title: `magic-context-dream-key-files-${args.sessionId.slice(0, 12)}`
|
|
24684
|
+
},
|
|
24685
|
+
query: { directory: args.sessionDirectory }
|
|
24686
|
+
});
|
|
24687
|
+
const created = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
|
|
24688
|
+
agentSessionId = typeof created?.id === "string" ? created.id : null;
|
|
24689
|
+
if (!agentSessionId) {
|
|
24690
|
+
throw new Error("Could not create key-file identification session.");
|
|
24691
|
+
}
|
|
24692
|
+
log(`[key-files][${args.sessionId}] child session created ${agentSessionId}`);
|
|
24693
|
+
const remainingMs = Math.max(0, args.deadline - Date.now());
|
|
24694
|
+
await promptSyncWithModelSuggestionRetry(args.client, {
|
|
24695
|
+
path: { id: agentSessionId },
|
|
24696
|
+
query: { directory: args.sessionDirectory },
|
|
24697
|
+
body: {
|
|
24698
|
+
agent: DREAMER_AGENT,
|
|
24699
|
+
system: KEY_FILES_SYSTEM_PROMPT,
|
|
24700
|
+
parts: [{ type: "text", text: prompt }]
|
|
24701
|
+
}
|
|
24702
|
+
}, { timeoutMs: Math.min(remainingMs, 5 * 60 * 1000), signal: abortController.signal });
|
|
24703
|
+
const messagesResponse = await args.client.session.messages({
|
|
24704
|
+
path: { id: agentSessionId },
|
|
24705
|
+
query: { directory: args.sessionDirectory }
|
|
24706
|
+
});
|
|
24707
|
+
const messages = normalizeSDKResponse(messagesResponse, [], {
|
|
24708
|
+
preferResponseOnMissingData: true
|
|
24709
|
+
});
|
|
24710
|
+
const responseText = extractLatestAssistantText(messages);
|
|
24711
|
+
if (!responseText) {
|
|
24712
|
+
log(`[key-files][${args.sessionId}] no response from agent \u2014 using heuristic fallback`);
|
|
24713
|
+
applyHeuristicFallback();
|
|
24714
|
+
return;
|
|
24715
|
+
}
|
|
24716
|
+
const parsed = parseKeyFilesOutput(responseText);
|
|
24717
|
+
if (parsed.length > 0 || hasExplicitEmptyKeyFilesOutput(responseText)) {
|
|
24718
|
+
const candidatePaths = new Set(candidates.map((c) => c.filePath));
|
|
24719
|
+
applyKeyFileResults(args.db, args.sessionId, parsed, args.config.token_budget, candidatePaths);
|
|
24720
|
+
return;
|
|
24721
|
+
}
|
|
24722
|
+
log(`[key-files][${args.sessionId}] could not parse agent output \u2014 using heuristic fallback`);
|
|
24723
|
+
applyHeuristicFallback();
|
|
24724
|
+
} catch (error48) {
|
|
24725
|
+
log(`[key-files][${args.sessionId}] identification failed: ${getErrorMessage(error48)} \u2014 using heuristic fallback`);
|
|
24726
|
+
try {
|
|
24727
|
+
applyHeuristicFallback();
|
|
24728
|
+
} catch (fallbackError) {
|
|
24729
|
+
log(`[key-files][${args.sessionId}] heuristic fallback failed: ${getErrorMessage(fallbackError)}`);
|
|
24730
|
+
}
|
|
24731
|
+
} finally {
|
|
24732
|
+
clearInterval(leaseInterval);
|
|
24733
|
+
if (agentSessionId) {
|
|
24734
|
+
await args.client.session.delete({
|
|
24735
|
+
path: { id: agentSessionId },
|
|
24736
|
+
query: { directory: args.sessionDirectory }
|
|
24737
|
+
}).catch((error48) => {
|
|
24738
|
+
log(`[key-files][${args.sessionId}] session cleanup failed: ${getErrorMessage(error48)}`);
|
|
24739
|
+
});
|
|
24740
|
+
}
|
|
24741
|
+
}
|
|
24742
|
+
} finally {
|
|
24743
|
+
if (openCodeDb) {
|
|
24744
|
+
try {
|
|
24745
|
+
openCodeDb.close(false);
|
|
24746
|
+
} catch (error48) {
|
|
24747
|
+
log(`[key-files][${args.sessionId}] failed to close OpenCode DB: ${getErrorMessage(error48)}`);
|
|
24748
|
+
}
|
|
24749
|
+
}
|
|
24750
|
+
}
|
|
24751
|
+
}
|
|
24752
|
+
async function identifyKeyFiles(args) {
|
|
24753
|
+
const sessionIds = await getActiveProjectSessionIds({
|
|
24754
|
+
db: args.db,
|
|
24755
|
+
client: args.client,
|
|
24756
|
+
projectIdentity: args.projectIdentity,
|
|
24757
|
+
sessionDirectory: args.sessionDirectory
|
|
24758
|
+
});
|
|
24759
|
+
if (sessionIds.length === 0) {
|
|
24760
|
+
log(`[key-files] no active sessions found for ${args.projectIdentity}`);
|
|
24761
|
+
return;
|
|
24762
|
+
}
|
|
24763
|
+
log(`[key-files] evaluating ${sessionIds.length} active session(s) for ${args.projectIdentity}`);
|
|
24764
|
+
for (const sessionId of sessionIds) {
|
|
24765
|
+
if (Date.now() > args.deadline) {
|
|
24766
|
+
log("[key-files] deadline reached \u2014 stopping key-file identification");
|
|
24767
|
+
break;
|
|
24768
|
+
}
|
|
24769
|
+
await identifyKeyFilesForSession({
|
|
24770
|
+
db: args.db,
|
|
24771
|
+
client: args.client,
|
|
24772
|
+
parentSessionId: args.parentSessionId,
|
|
24773
|
+
sessionDirectory: args.sessionDirectory,
|
|
24774
|
+
holderId: args.holderId,
|
|
24775
|
+
deadline: args.deadline,
|
|
24776
|
+
sessionId,
|
|
24777
|
+
config: args.config
|
|
24778
|
+
});
|
|
24779
|
+
}
|
|
24780
|
+
}
|
|
24406
24781
|
async function runDream(args) {
|
|
24407
24782
|
const holderId = crypto.randomUUID();
|
|
24408
24783
|
const startedAt = Date.now();
|
|
@@ -24474,8 +24849,8 @@ async function runDream(args) {
|
|
|
24474
24849
|
try {
|
|
24475
24850
|
const docsDir = args.sessionDirectory ?? args.projectIdentity;
|
|
24476
24851
|
const existingDocs = taskName === "maintain-docs" ? {
|
|
24477
|
-
architecture: existsSync4(
|
|
24478
|
-
structure: existsSync4(
|
|
24852
|
+
architecture: existsSync4(join7(docsDir, "ARCHITECTURE.md")),
|
|
24853
|
+
structure: existsSync4(join7(docsDir, "STRUCTURE.md"))
|
|
24479
24854
|
} : undefined;
|
|
24480
24855
|
const taskPrompt = buildDreamTaskPrompt(taskName, {
|
|
24481
24856
|
projectPath: args.projectIdentity,
|
|
@@ -24581,6 +24956,22 @@ async function runDream(args) {
|
|
|
24581
24956
|
log(`[dreamer] smart note evaluation failed: ${getErrorMessage(error48)}`);
|
|
24582
24957
|
}
|
|
24583
24958
|
}
|
|
24959
|
+
if (args.experimentalPinKeyFiles?.enabled && Date.now() <= deadline) {
|
|
24960
|
+
try {
|
|
24961
|
+
await identifyKeyFiles({
|
|
24962
|
+
db: args.db,
|
|
24963
|
+
client: args.client,
|
|
24964
|
+
projectIdentity: args.projectIdentity,
|
|
24965
|
+
parentSessionId,
|
|
24966
|
+
sessionDirectory: args.sessionDirectory ?? args.projectIdentity,
|
|
24967
|
+
holderId,
|
|
24968
|
+
deadline,
|
|
24969
|
+
config: args.experimentalPinKeyFiles
|
|
24970
|
+
});
|
|
24971
|
+
} catch (error48) {
|
|
24972
|
+
log(`[key-files] identification phase failed: ${getErrorMessage(error48)}`);
|
|
24973
|
+
}
|
|
24974
|
+
}
|
|
24584
24975
|
} finally {
|
|
24585
24976
|
releaseLease(args.db, holderId);
|
|
24586
24977
|
log(`[dreamer] lease released: ${holderId}`);
|
|
@@ -24789,7 +25180,8 @@ async function processDreamQueue(args) {
|
|
|
24789
25180
|
taskTimeoutMinutes: args.taskTimeoutMinutes,
|
|
24790
25181
|
maxRuntimeMinutes: args.maxRuntimeMinutes,
|
|
24791
25182
|
sessionDirectory: projectDirectory,
|
|
24792
|
-
experimentalUserMemories: args.experimentalUserMemories
|
|
25183
|
+
experimentalUserMemories: args.experimentalUserMemories,
|
|
25184
|
+
experimentalPinKeyFiles: args.experimentalPinKeyFiles
|
|
24793
25185
|
});
|
|
24794
25186
|
} catch (error48) {
|
|
24795
25187
|
log(`[dreamer] runDream threw for ${entry.projectIdentity}: ${getErrorMessage(error48)}`);
|
|
@@ -25286,7 +25678,7 @@ function getEmbeddingModelId() {
|
|
|
25286
25678
|
|
|
25287
25679
|
// src/features/magic-context/memory/project-identity.ts
|
|
25288
25680
|
import { execSync } from "child_process";
|
|
25289
|
-
import
|
|
25681
|
+
import path3 from "path";
|
|
25290
25682
|
var GIT_TIMEOUT_MS = 5000;
|
|
25291
25683
|
var resolvedCache = new Map;
|
|
25292
25684
|
function getRootCommitHash(directory) {
|
|
@@ -25305,12 +25697,12 @@ function getRootCommitHash(directory) {
|
|
|
25305
25697
|
}
|
|
25306
25698
|
}
|
|
25307
25699
|
function directoryFallback(directory) {
|
|
25308
|
-
const canonical =
|
|
25700
|
+
const canonical = path3.resolve(directory);
|
|
25309
25701
|
const hash2 = Bun.hash(canonical).toString(16).slice(0, 12);
|
|
25310
25702
|
return `dir:${hash2}`;
|
|
25311
25703
|
}
|
|
25312
25704
|
function resolveProjectIdentity(directory) {
|
|
25313
|
-
const resolved =
|
|
25705
|
+
const resolved = path3.resolve(directory);
|
|
25314
25706
|
const cached2 = resolvedCache.get(resolved);
|
|
25315
25707
|
if (cached2 !== undefined) {
|
|
25316
25708
|
return cached2;
|
|
@@ -25397,22 +25789,10 @@ function removeSystemReminders(text) {
|
|
|
25397
25789
|
}
|
|
25398
25790
|
|
|
25399
25791
|
// src/hooks/magic-context/read-session-db.ts
|
|
25400
|
-
import { Database } from "bun:sqlite";
|
|
25792
|
+
import { Database as Database2 } from "bun:sqlite";
|
|
25401
25793
|
import { join as join8 } from "path";
|
|
25402
|
-
|
|
25403
|
-
// src/shared/data-path.ts
|
|
25404
|
-
import * as os2 from "os";
|
|
25405
|
-
import * as path3 from "path";
|
|
25406
|
-
function getDataDir() {
|
|
25407
|
-
return process.env.XDG_DATA_HOME ?? path3.join(os2.homedir(), ".local", "share");
|
|
25408
|
-
}
|
|
25409
|
-
function getOpenCodeStorageDir() {
|
|
25410
|
-
return path3.join(getDataDir(), "opencode", "storage");
|
|
25411
|
-
}
|
|
25412
|
-
|
|
25413
|
-
// src/hooks/magic-context/read-session-db.ts
|
|
25414
25794
|
init_logger();
|
|
25415
|
-
function
|
|
25795
|
+
function getOpenCodeDbPath2() {
|
|
25416
25796
|
return join8(getDataDir(), "opencode", "opencode.db");
|
|
25417
25797
|
}
|
|
25418
25798
|
var cachedReadOnlyDb = null;
|
|
@@ -25429,12 +25809,12 @@ function closeCachedReadOnlyDb() {
|
|
|
25429
25809
|
}
|
|
25430
25810
|
}
|
|
25431
25811
|
function getReadOnlySessionDb() {
|
|
25432
|
-
const dbPath =
|
|
25812
|
+
const dbPath = getOpenCodeDbPath2();
|
|
25433
25813
|
if (cachedReadOnlyDb?.path === dbPath) {
|
|
25434
25814
|
return cachedReadOnlyDb.db;
|
|
25435
25815
|
}
|
|
25436
25816
|
closeCachedReadOnlyDb();
|
|
25437
|
-
const db = new
|
|
25817
|
+
const db = new Database2(dbPath, { readonly: true });
|
|
25438
25818
|
cachedReadOnlyDb = { path: dbPath, db };
|
|
25439
25819
|
return db;
|
|
25440
25820
|
}
|
|
@@ -25877,7 +26257,6 @@ var upsertIndexStatements = new WeakMap;
|
|
|
25877
26257
|
var deleteFtsStatements = new WeakMap;
|
|
25878
26258
|
var deleteIndexStatements = new WeakMap;
|
|
25879
26259
|
var countIndexedMessageStatements = new WeakMap;
|
|
25880
|
-
var deleteIndexedMessageStatements = new WeakMap;
|
|
25881
26260
|
function normalizeIndexText(text) {
|
|
25882
26261
|
return text.replace(/\s+/g, " ").trim();
|
|
25883
26262
|
}
|
|
@@ -25929,14 +26308,6 @@ function getCountIndexedMessageStatement(db) {
|
|
|
25929
26308
|
}
|
|
25930
26309
|
return stmt;
|
|
25931
26310
|
}
|
|
25932
|
-
function getDeleteIndexedMessageStatement(db) {
|
|
25933
|
-
let stmt = deleteIndexedMessageStatements.get(db);
|
|
25934
|
-
if (!stmt) {
|
|
25935
|
-
stmt = db.prepare("DELETE FROM message_history_fts WHERE session_id = ? AND message_id = ?");
|
|
25936
|
-
deleteIndexedMessageStatements.set(db, stmt);
|
|
25937
|
-
}
|
|
25938
|
-
return stmt;
|
|
25939
|
-
}
|
|
25940
26311
|
function getLastIndexedOrdinal(db, sessionId) {
|
|
25941
26312
|
const row = getLastIndexedStatement(db).get(sessionId);
|
|
25942
26313
|
return typeof row?.last_indexed_ordinal === "number" ? row.last_indexed_ordinal : 0;
|
|
@@ -25944,10 +26315,7 @@ function getLastIndexedOrdinal(db, sessionId) {
|
|
|
25944
26315
|
function deleteIndexedMessage(db, sessionId, messageId) {
|
|
25945
26316
|
const row = getCountIndexedMessageStatement(db).get(sessionId, messageId);
|
|
25946
26317
|
const count = typeof row?.count === "number" ? row.count : 0;
|
|
25947
|
-
|
|
25948
|
-
getDeleteIndexedMessageStatement(db).run(sessionId, messageId);
|
|
25949
|
-
}
|
|
25950
|
-
getDeleteIndexStatement(db).run(sessionId);
|
|
26318
|
+
clearIndexedMessages(db, sessionId);
|
|
25951
26319
|
return count;
|
|
25952
26320
|
}
|
|
25953
26321
|
function clearIndexedMessages(db, sessionId) {
|
|
@@ -25999,7 +26367,7 @@ function ensureMessagesIndexed(db, sessionId, readMessages) {
|
|
|
25999
26367
|
})();
|
|
26000
26368
|
}
|
|
26001
26369
|
// src/features/magic-context/storage-db.ts
|
|
26002
|
-
import { Database as
|
|
26370
|
+
import { Database as Database3 } from "bun:sqlite";
|
|
26003
26371
|
import { mkdirSync } from "fs";
|
|
26004
26372
|
import { join as join9 } from "path";
|
|
26005
26373
|
init_logger();
|
|
@@ -26435,6 +26803,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
|
|
|
26435
26803
|
ensureColumn(db, "tags", "reasoning_byte_size", "INTEGER DEFAULT 0");
|
|
26436
26804
|
ensureColumn(db, "session_meta", "system_prompt_tokens", "INTEGER DEFAULT 0");
|
|
26437
26805
|
ensureColumn(db, "session_meta", "compaction_marker_state", "TEXT DEFAULT ''");
|
|
26806
|
+
ensureColumn(db, "session_meta", "key_files", "TEXT DEFAULT ''");
|
|
26438
26807
|
}
|
|
26439
26808
|
function ensureColumn(db, table, column, definition) {
|
|
26440
26809
|
if (!/^[a-z_]+$/.test(table) || !/^[a-z_]+$/.test(column) || !/^[A-Z0-9_'(),\s]+$/i.test(definition)) {
|
|
@@ -26448,7 +26817,7 @@ function ensureColumn(db, table, column, definition) {
|
|
|
26448
26817
|
}
|
|
26449
26818
|
function createFallbackDatabase() {
|
|
26450
26819
|
try {
|
|
26451
|
-
const fallback = new
|
|
26820
|
+
const fallback = new Database3(":memory:");
|
|
26452
26821
|
initializeDatabase(fallback);
|
|
26453
26822
|
runMigrations(fallback);
|
|
26454
26823
|
return fallback;
|
|
@@ -26467,7 +26836,7 @@ function openDatabase() {
|
|
|
26467
26836
|
return existing;
|
|
26468
26837
|
}
|
|
26469
26838
|
mkdirSync(dbDir, { recursive: true });
|
|
26470
|
-
const db = new
|
|
26839
|
+
const db = new Database3(dbPath);
|
|
26471
26840
|
initializeDatabase(db);
|
|
26472
26841
|
runMigrations(db);
|
|
26473
26842
|
databases.set(dbPath, db);
|
|
@@ -27019,7 +27388,8 @@ function startDreamScheduleTimer(args) {
|
|
|
27019
27388
|
dreamerConfig,
|
|
27020
27389
|
embeddingConfig: embeddingConfig2,
|
|
27021
27390
|
memoryEnabled,
|
|
27022
|
-
experimentalUserMemories
|
|
27391
|
+
experimentalUserMemories,
|
|
27392
|
+
experimentalPinKeyFiles
|
|
27023
27393
|
} = args;
|
|
27024
27394
|
const dreamingEnabled = Boolean(dreamerConfig?.enabled && dreamerConfig.schedule?.trim());
|
|
27025
27395
|
const embeddingSweepEnabled = memoryEnabled && embeddingConfig2.provider !== "off";
|
|
@@ -27049,7 +27419,8 @@ function startDreamScheduleTimer(args) {
|
|
|
27049
27419
|
tasks: dreamerConfig.tasks,
|
|
27050
27420
|
taskTimeoutMinutes: dreamerConfig.task_timeout_minutes,
|
|
27051
27421
|
maxRuntimeMinutes: dreamerConfig.max_runtime_minutes,
|
|
27052
|
-
experimentalUserMemories
|
|
27422
|
+
experimentalUserMemories,
|
|
27423
|
+
experimentalPinKeyFiles
|
|
27053
27424
|
}).catch((error48) => {
|
|
27054
27425
|
log("[dreamer] timer-triggered queue processing failed:", error48);
|
|
27055
27426
|
});
|
|
@@ -27848,7 +28219,8 @@ Dreaming is not configured for this project.`, {});
|
|
|
27848
28219
|
tasks: deps.dreamer.config.tasks,
|
|
27849
28220
|
taskTimeoutMinutes: deps.dreamer.config.task_timeout_minutes,
|
|
27850
28221
|
maxRuntimeMinutes: deps.dreamer.config.max_runtime_minutes,
|
|
27851
|
-
experimentalUserMemories: deps.dreamer.experimentalUserMemories
|
|
28222
|
+
experimentalUserMemories: deps.dreamer.experimentalUserMemories,
|
|
28223
|
+
experimentalPinKeyFiles: deps.dreamer.experimentalPinKeyFiles
|
|
27852
28224
|
});
|
|
27853
28225
|
await deps.sendNotification(sessionId, result ? summarizeDreamResult(result) : "Dream queued, but another worker is already processing the queue.", {});
|
|
27854
28226
|
throw new Error(`${SENTINEL_PREFIX}CTX-DREAM_HANDLED__`);
|
|
@@ -27937,7 +28309,7 @@ Historian recomp started. Rebuilding compartments and facts from raw session his
|
|
|
27937
28309
|
init_logger();
|
|
27938
28310
|
|
|
27939
28311
|
// src/features/magic-context/compaction-marker.ts
|
|
27940
|
-
import { Database as
|
|
28312
|
+
import { Database as Database4 } from "bun:sqlite";
|
|
27941
28313
|
import { join as join10 } from "path";
|
|
27942
28314
|
init_logger();
|
|
27943
28315
|
var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
@@ -27959,12 +28331,12 @@ function generateMessageId(timestampMs, counter = 0n) {
|
|
|
27959
28331
|
function generatePartId(timestampMs, counter = 0n) {
|
|
27960
28332
|
return generateId("prt", timestampMs, counter);
|
|
27961
28333
|
}
|
|
27962
|
-
function
|
|
28334
|
+
function getOpenCodeDbPath3() {
|
|
27963
28335
|
return join10(getDataDir(), "opencode", "opencode.db");
|
|
27964
28336
|
}
|
|
27965
28337
|
var cachedWriteDb = null;
|
|
27966
28338
|
function getWritableOpenCodeDb() {
|
|
27967
|
-
const dbPath =
|
|
28339
|
+
const dbPath = getOpenCodeDbPath3();
|
|
27968
28340
|
if (cachedWriteDb?.path === dbPath) {
|
|
27969
28341
|
return cachedWriteDb.db;
|
|
27970
28342
|
}
|
|
@@ -27973,7 +28345,7 @@ function getWritableOpenCodeDb() {
|
|
|
27973
28345
|
cachedWriteDb.db.close(false);
|
|
27974
28346
|
} catch {}
|
|
27975
28347
|
}
|
|
27976
|
-
const db = new
|
|
28348
|
+
const db = new Database4(dbPath);
|
|
27977
28349
|
db.exec("PRAGMA journal_mode=WAL");
|
|
27978
28350
|
db.exec("PRAGMA busy_timeout=5000");
|
|
27979
28351
|
cachedWriteDb = { path: dbPath, db };
|
|
@@ -28348,7 +28720,7 @@ function createEventHandler2(deps) {
|
|
|
28348
28720
|
const contextLimit = resolveContextLimit(info.providerID, info.modelID, {
|
|
28349
28721
|
modelContextLimitsCache: deps.config.modelContextLimitsCache
|
|
28350
28722
|
});
|
|
28351
|
-
const percentage = totalInputTokens / contextLimit * 100;
|
|
28723
|
+
const percentage = contextLimit > 0 ? totalInputTokens / contextLimit * 100 : 0;
|
|
28352
28724
|
sessionLog(info.sessionID, `event message.updated: totalInputTokens=${totalInputTokens} contextLimit=${contextLimit} percentage=${percentage.toFixed(1)}%`);
|
|
28353
28725
|
deps.contextUsageMap.set(info.sessionID, {
|
|
28354
28726
|
usage: {
|
|
@@ -31569,14 +31941,8 @@ function runPostTransformPhase(args) {
|
|
|
31569
31941
|
const reinjected = appendReminderToUserMessageById(args.messages, pendingUserTurnReminder.messageId, pendingUserTurnReminder.text);
|
|
31570
31942
|
if (!reinjected) {
|
|
31571
31943
|
if (isCacheBustingPass) {
|
|
31572
|
-
|
|
31573
|
-
|
|
31574
|
-
setPersistedStickyTurnReminder(args.db, args.sessionId, pendingUserTurnReminder.text, newAnchorId);
|
|
31575
|
-
sessionLog(args.sessionId, `sticky turn reminder re-anchored: ${pendingUserTurnReminder.messageId} \u2192 ${newAnchorId}`);
|
|
31576
|
-
} else {
|
|
31577
|
-
clearPersistedStickyTurnReminder(args.db, args.sessionId);
|
|
31578
|
-
sessionLog(args.sessionId, `sticky turn reminder cleared \u2014 anchor ${pendingUserTurnReminder.messageId} gone and no user message visible`);
|
|
31579
|
-
}
|
|
31944
|
+
clearPersistedStickyTurnReminder(args.db, args.sessionId);
|
|
31945
|
+
sessionLog(args.sessionId, `sticky turn reminder cleared \u2014 anchor ${pendingUserTurnReminder.messageId} gone (compacted/deleted)`);
|
|
31580
31946
|
} else {
|
|
31581
31947
|
sessionLog(args.sessionId, `preserving sticky turn reminder anchor to avoid cache bust: messageId=${pendingUserTurnReminder.messageId}`);
|
|
31582
31948
|
}
|
|
@@ -31617,13 +31983,8 @@ function runPostTransformPhase(args) {
|
|
|
31617
31983
|
const reinjected = appendReminderToUserMessageById(args.messages, stickyNoteNudge.messageId, stickyNoteNudge.text);
|
|
31618
31984
|
if (!reinjected) {
|
|
31619
31985
|
if (isCacheBustingPass) {
|
|
31620
|
-
|
|
31621
|
-
|
|
31622
|
-
markNoteNudgeDelivered(args.db, args.sessionId, stickyNoteNudge.text, newAnchorId);
|
|
31623
|
-
sessionLog(args.sessionId, `sticky note nudge re-anchored: ${stickyNoteNudge.messageId} \u2192 ${newAnchorId}`);
|
|
31624
|
-
} else {
|
|
31625
|
-
sessionLog(args.sessionId, `sticky note nudge anchor ${stickyNoteNudge.messageId} gone \u2014 no user message visible to re-anchor`);
|
|
31626
|
-
}
|
|
31986
|
+
clearNoteNudgeState(args.db, args.sessionId);
|
|
31987
|
+
sessionLog(args.sessionId, `sticky note nudge cleared \u2014 anchor ${stickyNoteNudge.messageId} gone (compacted/deleted)`);
|
|
31627
31988
|
} else {
|
|
31628
31989
|
sessionLog(args.sessionId, `preserving sticky note nudge anchor to avoid cache bust: messageId=${stickyNoteNudge.messageId}`);
|
|
31629
31990
|
}
|
|
@@ -31983,8 +32344,8 @@ function createToolExecuteAfterHook(args) {
|
|
|
31983
32344
|
}
|
|
31984
32345
|
|
|
31985
32346
|
// src/hooks/magic-context/system-prompt-hash.ts
|
|
31986
|
-
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
31987
|
-
import { join as join12 } from "path";
|
|
32347
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync } from "fs";
|
|
32348
|
+
import { join as join12, resolve as resolve2, sep } from "path";
|
|
31988
32349
|
|
|
31989
32350
|
// src/agents/magic-context-prompt.ts
|
|
31990
32351
|
var BASE_INTRO = (protectedTags) => `Messages and tool outputs are tagged with \xA7N\xA7 identifiers (e.g., \xA71\xA7, \xA742\xA7).
|
|
@@ -32186,7 +32547,9 @@ init_logger();
|
|
|
32186
32547
|
var MAGIC_CONTEXT_MARKER = "## Magic Context";
|
|
32187
32548
|
var PROJECT_DOCS_MARKER = "<project-docs>";
|
|
32188
32549
|
var USER_PROFILE_MARKER = "<user-profile>";
|
|
32550
|
+
var KEY_FILES_MARKER = "<key-files>";
|
|
32189
32551
|
var cachedUserProfileBySession = new Map;
|
|
32552
|
+
var cachedKeyFilesBySession = new Map;
|
|
32190
32553
|
var DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
|
|
32191
32554
|
function readProjectDocs(directory) {
|
|
32192
32555
|
const sections = [];
|
|
@@ -32268,6 +32631,72 @@ ${items}
|
|
|
32268
32631
|
output.system.push(profileBlock);
|
|
32269
32632
|
}
|
|
32270
32633
|
}
|
|
32634
|
+
if (deps.experimentalPinKeyFiles) {
|
|
32635
|
+
const hasCachedKeyFiles = cachedKeyFilesBySession.has(sessionId);
|
|
32636
|
+
if (!hasCachedKeyFiles || isCacheBusting) {
|
|
32637
|
+
const keyFileEntries = getKeyFiles(deps.db, sessionId);
|
|
32638
|
+
if (keyFileEntries.length > 0) {
|
|
32639
|
+
const sections = [];
|
|
32640
|
+
const projectRoot = resolve2(deps.directory);
|
|
32641
|
+
let remainingBudgetTokens = deps.experimentalPinKeyFilesTokenBudget ?? 1e4;
|
|
32642
|
+
for (const entry of keyFileEntries) {
|
|
32643
|
+
try {
|
|
32644
|
+
const absPath = resolve2(deps.directory, entry.filePath);
|
|
32645
|
+
if (!absPath.startsWith(projectRoot + sep) && absPath !== projectRoot) {
|
|
32646
|
+
log(`[magic-context] key file path escapes project root, skipping: ${entry.filePath}`);
|
|
32647
|
+
continue;
|
|
32648
|
+
}
|
|
32649
|
+
if (!existsSync5(absPath))
|
|
32650
|
+
continue;
|
|
32651
|
+
let realPath;
|
|
32652
|
+
try {
|
|
32653
|
+
realPath = realpathSync(absPath);
|
|
32654
|
+
} catch {
|
|
32655
|
+
continue;
|
|
32656
|
+
}
|
|
32657
|
+
if (!realPath.startsWith(projectRoot + sep) && realPath !== projectRoot) {
|
|
32658
|
+
log(`[magic-context] key file symlink escapes project root, skipping: ${entry.filePath} \u2192 ${realPath}`);
|
|
32659
|
+
continue;
|
|
32660
|
+
}
|
|
32661
|
+
const content = readFileSync4(realPath, "utf-8").trim();
|
|
32662
|
+
if (content.length === 0)
|
|
32663
|
+
continue;
|
|
32664
|
+
const fileTokens = estimateTokens(content);
|
|
32665
|
+
if (fileTokens > remainingBudgetTokens) {
|
|
32666
|
+
log(`[magic-context] key file ${entry.filePath} exceeds remaining budget (${fileTokens} > ${remainingBudgetTokens}), skipping`);
|
|
32667
|
+
continue;
|
|
32668
|
+
}
|
|
32669
|
+
remainingBudgetTokens -= fileTokens;
|
|
32670
|
+
sections.push(`<file path="${escapeXmlAttr(entry.filePath)}">
|
|
32671
|
+
${escapeXmlContent(content)}
|
|
32672
|
+
</file>`);
|
|
32673
|
+
} catch (error48) {
|
|
32674
|
+
log(`[magic-context] failed to read key file ${entry.filePath}:`, error48);
|
|
32675
|
+
}
|
|
32676
|
+
}
|
|
32677
|
+
if (sections.length > 0) {
|
|
32678
|
+
cachedKeyFilesBySession.set(sessionId, `${KEY_FILES_MARKER}
|
|
32679
|
+
${sections.join(`
|
|
32680
|
+
|
|
32681
|
+
`)}
|
|
32682
|
+
</key-files>`);
|
|
32683
|
+
if (!hasCachedKeyFiles) {
|
|
32684
|
+
sessionLog(sessionId, `loaded ${sections.length} key file(s) into system prompt`);
|
|
32685
|
+
} else {
|
|
32686
|
+
sessionLog(sessionId, "refreshed key files (cache-busting pass)");
|
|
32687
|
+
}
|
|
32688
|
+
} else {
|
|
32689
|
+
cachedKeyFilesBySession.set(sessionId, null);
|
|
32690
|
+
}
|
|
32691
|
+
} else {
|
|
32692
|
+
cachedKeyFilesBySession.set(sessionId, null);
|
|
32693
|
+
}
|
|
32694
|
+
}
|
|
32695
|
+
const keyFilesBlock = cachedKeyFilesBySession.get(sessionId);
|
|
32696
|
+
if (keyFilesBlock && !fullPrompt.includes(KEY_FILES_MARKER)) {
|
|
32697
|
+
output.system.push(keyFilesBlock);
|
|
32698
|
+
}
|
|
32699
|
+
}
|
|
32271
32700
|
const DATE_PATTERN = /Today's date: .+/;
|
|
32272
32701
|
for (let i = 0;i < output.system.length; i++) {
|
|
32273
32702
|
const match = output.system[i].match(DATE_PATTERN);
|
|
@@ -32308,7 +32737,7 @@ ${items}
|
|
|
32308
32737
|
} else if (previousHash === "" || previousHash === "0") {
|
|
32309
32738
|
sessionLog(sessionId, `system prompt hash initialized: ${currentHash} (len=${systemContent.length})`);
|
|
32310
32739
|
}
|
|
32311
|
-
const systemPromptTokens =
|
|
32740
|
+
const systemPromptTokens = estimateTokens(systemContent);
|
|
32312
32741
|
if (currentHash !== previousHash) {
|
|
32313
32742
|
updateSessionMeta(deps.db, sessionId, {
|
|
32314
32743
|
systemPromptHash: currentHash,
|
|
@@ -32442,6 +32871,11 @@ function createMagicContextHook(deps) {
|
|
|
32442
32871
|
experimentalUserMemories: deps.config.experimental?.user_memories?.enabled ? {
|
|
32443
32872
|
enabled: true,
|
|
32444
32873
|
promotionThreshold: deps.config.experimental.user_memories?.promotion_threshold
|
|
32874
|
+
} : undefined,
|
|
32875
|
+
experimentalPinKeyFiles: deps.config.experimental?.pin_key_files?.enabled ? {
|
|
32876
|
+
enabled: true,
|
|
32877
|
+
token_budget: deps.config.experimental.pin_key_files?.token_budget,
|
|
32878
|
+
min_reads: deps.config.experimental.pin_key_files?.min_reads
|
|
32445
32879
|
} : undefined
|
|
32446
32880
|
}).catch((error48) => {
|
|
32447
32881
|
log("[dreamer] scheduled queue processing failed:", error48);
|
|
@@ -32488,6 +32922,11 @@ function createMagicContextHook(deps) {
|
|
|
32488
32922
|
experimentalUserMemories: deps.config.experimental?.user_memories?.enabled ? {
|
|
32489
32923
|
enabled: true,
|
|
32490
32924
|
promotionThreshold: deps.config.experimental.user_memories?.promotion_threshold
|
|
32925
|
+
} : undefined,
|
|
32926
|
+
experimentalPinKeyFiles: deps.config.experimental?.pin_key_files?.enabled ? {
|
|
32927
|
+
enabled: true,
|
|
32928
|
+
token_budget: deps.config.experimental.pin_key_files?.token_budget,
|
|
32929
|
+
min_reads: deps.config.experimental.pin_key_files?.min_reads
|
|
32491
32930
|
} : undefined
|
|
32492
32931
|
} : undefined
|
|
32493
32932
|
});
|
|
@@ -32501,7 +32940,9 @@ function createMagicContextHook(deps) {
|
|
|
32501
32940
|
directory: deps.directory,
|
|
32502
32941
|
flushedSessions,
|
|
32503
32942
|
lastHeuristicsTurnId,
|
|
32504
|
-
experimentalUserMemories: deps.config.experimental?.user_memories?.enabled
|
|
32943
|
+
experimentalUserMemories: deps.config.experimental?.user_memories?.enabled,
|
|
32944
|
+
experimentalPinKeyFiles: deps.config.experimental?.pin_key_files?.enabled ?? false,
|
|
32945
|
+
experimentalPinKeyFilesTokenBudget: deps.config.experimental?.pin_key_files?.token_budget
|
|
32505
32946
|
});
|
|
32506
32947
|
const eventHook = createEventHook({
|
|
32507
32948
|
eventHandler,
|
|
@@ -32818,6 +33259,7 @@ function createCtxMemoryTool(deps) {
|
|
|
32818
33259
|
memoryId: memory.id,
|
|
32819
33260
|
content
|
|
32820
33261
|
});
|
|
33262
|
+
invalidateAllMemoryBlockCaches(deps.db);
|
|
32821
33263
|
return `Saved memory [ID: ${memory.id}] in ${category}.`;
|
|
32822
33264
|
}
|
|
32823
33265
|
if (args.action === "delete") {
|
|
@@ -32829,6 +33271,7 @@ function createCtxMemoryTool(deps) {
|
|
|
32829
33271
|
return `Error: Memory with ID ${args.id} was not found.`;
|
|
32830
33272
|
}
|
|
32831
33273
|
archiveMemory(deps.db, args.id);
|
|
33274
|
+
invalidateAllMemoryBlockCaches(deps.db);
|
|
32832
33275
|
return `Archived memory [ID: ${args.id}].`;
|
|
32833
33276
|
}
|
|
32834
33277
|
if (args.action === "list") {
|
|
@@ -32861,6 +33304,7 @@ function createCtxMemoryTool(deps) {
|
|
|
32861
33304
|
memoryId: memory.id,
|
|
32862
33305
|
content
|
|
32863
33306
|
});
|
|
33307
|
+
invalidateAllMemoryBlockCaches(deps.db);
|
|
32864
33308
|
return `Updated memory [ID: ${memory.id}] in ${memory.category}.`;
|
|
32865
33309
|
}
|
|
32866
33310
|
if (args.action === "merge") {
|
|
@@ -32933,6 +33377,7 @@ function createCtxMemoryTool(deps) {
|
|
|
32933
33377
|
memoryId: canonicalMemory.id,
|
|
32934
33378
|
content
|
|
32935
33379
|
});
|
|
33380
|
+
invalidateAllMemoryBlockCaches(deps.db);
|
|
32936
33381
|
const supersededIds = sourceMemories.map((memory) => memory.id).filter((id) => id !== canonicalMemory.id);
|
|
32937
33382
|
return `Merged memories [${ids.join(", ")}] into canonical memory [ID: ${canonicalMemory.id}] in ${category}; superseded [${supersededIds.join(", ")}].`;
|
|
32938
33383
|
}
|
|
@@ -32945,6 +33390,7 @@ function createCtxMemoryTool(deps) {
|
|
|
32945
33390
|
return `Error: Memory with ID ${args.id} was not found.`;
|
|
32946
33391
|
}
|
|
32947
33392
|
archiveMemory(deps.db, args.id, args.reason);
|
|
33393
|
+
invalidateAllMemoryBlockCaches(deps.db);
|
|
32948
33394
|
return args.reason?.trim() ? `Archived memory [ID: ${args.id}] (${args.reason.trim()}).` : `Archived memory [ID: ${args.id}].`;
|
|
32949
33395
|
}
|
|
32950
33396
|
return "Error: Unknown action.";
|
|
@@ -32975,15 +33421,17 @@ Historian reads these notes, deduplicates them, and rewrites the remaining usefu
|
|
|
32975
33421
|
import { tool as tool3 } from "@opencode-ai/plugin";
|
|
32976
33422
|
function formatNoteLine(note) {
|
|
32977
33423
|
const statusSuffix = note.status === "active" ? "" : ` (${note.status})`;
|
|
32978
|
-
const dismissHint = note.status === "dismissed" ? "" : ` _(dismiss with \`ctx_note(action="dismiss", note_id=${note.id})\`)_`;
|
|
32979
33424
|
if (note.type === "session") {
|
|
32980
|
-
return `- **#${note.id}**${statusSuffix}: ${note.content}
|
|
33425
|
+
return `- **#${note.id}**${statusSuffix}: ${note.content}`;
|
|
32981
33426
|
}
|
|
32982
33427
|
const conditionText = note.status === "ready" ? note.readyReason ?? note.surfaceCondition ?? "Condition satisfied" : note.surfaceCondition ?? "No condition recorded";
|
|
32983
33428
|
const conditionLabel = note.status === "ready" ? "Condition met" : "Condition";
|
|
32984
33429
|
return `- **#${note.id}**${statusSuffix}: ${note.content}
|
|
32985
|
-
${conditionLabel}: ${conditionText}
|
|
33430
|
+
${conditionLabel}: ${conditionText}`;
|
|
32986
33431
|
}
|
|
33432
|
+
var DISMISS_FOOTER = `
|
|
33433
|
+
|
|
33434
|
+
To dismiss a stale note: ctx_note(action="dismiss", note_id=N)`;
|
|
32987
33435
|
function buildReadSections(args) {
|
|
32988
33436
|
if (args.filter === undefined) {
|
|
32989
33437
|
const sessionNotes2 = getSessionNotes(args.db, args.sessionId);
|
|
@@ -33122,7 +33570,7 @@ No session notes or smart notes.`;
|
|
|
33122
33570
|
}
|
|
33123
33571
|
return sections.join(`
|
|
33124
33572
|
|
|
33125
|
-
`);
|
|
33573
|
+
`) + DISMISS_FOOTER;
|
|
33126
33574
|
}
|
|
33127
33575
|
});
|
|
33128
33576
|
}
|
|
@@ -33893,6 +34341,11 @@ var plugin = async (ctx) => {
|
|
|
33893
34341
|
experimentalUserMemories: pluginConfig.experimental?.user_memories?.enabled ? {
|
|
33894
34342
|
enabled: true,
|
|
33895
34343
|
promotionThreshold: pluginConfig.experimental.user_memories?.promotion_threshold
|
|
34344
|
+
} : undefined,
|
|
34345
|
+
experimentalPinKeyFiles: pluginConfig.experimental?.pin_key_files?.enabled ? {
|
|
34346
|
+
enabled: true,
|
|
34347
|
+
token_budget: pluginConfig.experimental.pin_key_files?.token_budget,
|
|
34348
|
+
min_reads: pluginConfig.experimental.pin_key_files?.min_reads
|
|
33896
34349
|
} : undefined
|
|
33897
34350
|
});
|
|
33898
34351
|
startTuiActionConsumer({
|