@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.
Files changed (32) hide show
  1. package/README.md +2 -0
  2. package/dist/config/schema/magic-context.d.ts +17 -0
  3. package/dist/config/schema/magic-context.d.ts.map +1 -1
  4. package/dist/features/magic-context/compartment-storage.d.ts +6 -0
  5. package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
  6. package/dist/features/magic-context/dreamer/runner.d.ts +9 -1
  7. package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
  8. package/dist/features/magic-context/key-files/identify-key-files.d.ts +39 -0
  9. package/dist/features/magic-context/key-files/identify-key-files.d.ts.map +1 -0
  10. package/dist/features/magic-context/key-files/read-stats.d.ts +20 -0
  11. package/dist/features/magic-context/key-files/read-stats.d.ts.map +1 -0
  12. package/dist/features/magic-context/key-files/storage-key-files.d.ts +25 -0
  13. package/dist/features/magic-context/key-files/storage-key-files.d.ts.map +1 -0
  14. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  15. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  16. package/dist/hooks/magic-context/command-handler.d.ts +5 -0
  17. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  18. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  19. package/dist/hooks/magic-context/hook.d.ts +5 -0
  20. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  21. package/dist/hooks/magic-context/system-prompt-hash.d.ts +4 -0
  22. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  23. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +525 -72
  26. package/dist/plugin/dream-timer.d.ts +5 -0
  27. package/dist/plugin/dream-timer.d.ts.map +1 -1
  28. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  29. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  30. package/dist/tools/ctx-note/tools.d.ts.map +1 -1
  31. package/package.json +1 -1
  32. 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 join6 } from "path";
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(join6(docsDir, "ARCHITECTURE.md")),
24478
- structure: existsSync4(join6(docsDir, "STRUCTURE.md"))
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 path2 from "path";
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 = path2.resolve(directory);
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 = path2.resolve(directory);
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 getOpenCodeDbPath() {
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 = getOpenCodeDbPath();
25812
+ const dbPath = getOpenCodeDbPath2();
25433
25813
  if (cachedReadOnlyDb?.path === dbPath) {
25434
25814
  return cachedReadOnlyDb.db;
25435
25815
  }
25436
25816
  closeCachedReadOnlyDb();
25437
- const db = new Database(dbPath, { readonly: true });
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
- if (count > 0) {
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 Database2 } from "bun:sqlite";
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 Database2(":memory:");
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 Database2(dbPath);
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 Database3 } from "bun:sqlite";
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 getOpenCodeDbPath2() {
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 = getOpenCodeDbPath2();
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 Database3(dbPath);
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
- const newAnchorId = appendReminderToLatestUserMessage(args.messages, pendingUserTurnReminder.text);
31573
- if (newAnchorId) {
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
- const newAnchorId = appendReminderToLatestUserMessage(args.messages, stickyNoteNudge.text);
31621
- if (newAnchorId) {
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 = Math.ceil(systemContent.length / 4);
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}${dismissHint}`;
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}${dismissHint}`;
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({