@cortexkit/opencode-magic-context 0.19.0 → 0.20.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 (44) hide show
  1. package/README.md +2 -2
  2. package/dist/features/magic-context/dreamer/lease.d.ts +1 -0
  3. package/dist/features/magic-context/dreamer/lease.d.ts.map +1 -1
  4. package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
  5. package/dist/features/magic-context/key-files/aft-availability.d.ts +11 -0
  6. package/dist/features/magic-context/key-files/aft-availability.d.ts.map +1 -0
  7. package/dist/features/magic-context/key-files/identify-key-files.d.ts +45 -0
  8. package/dist/features/magic-context/key-files/identify-key-files.d.ts.map +1 -1
  9. package/dist/features/magic-context/key-files/project-key-files.d.ts +42 -0
  10. package/dist/features/magic-context/key-files/project-key-files.d.ts.map +1 -0
  11. package/dist/features/magic-context/key-files/read-history.d.ts +26 -0
  12. package/dist/features/magic-context/key-files/read-history.d.ts.map +1 -0
  13. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  14. package/dist/features/magic-context/overflow-detection.d.ts +1 -1
  15. package/dist/features/magic-context/sidekick/agent.d.ts.map +1 -1
  16. package/dist/features/magic-context/storage-db.d.ts +1 -0
  17. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  18. package/dist/features/magic-context/storage-meta-persisted.d.ts +8 -0
  19. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  20. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  21. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  22. package/dist/features/magic-context/storage.d.ts +1 -1
  23. package/dist/features/magic-context/storage.d.ts.map +1 -1
  24. package/dist/hooks/auto-update-checker/cache.d.ts.map +1 -1
  25. package/dist/hooks/magic-context/boundary-execution.d.ts +24 -0
  26. package/dist/hooks/magic-context/boundary-execution.d.ts.map +1 -0
  27. package/dist/hooks/magic-context/event-handler.d.ts +1 -0
  28. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  29. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  30. package/dist/hooks/magic-context/key-files-block.d.ts +27 -0
  31. package/dist/hooks/magic-context/key-files-block.d.ts.map +1 -0
  32. package/dist/hooks/magic-context/read-session-db.d.ts +2 -0
  33. package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
  34. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  35. package/dist/hooks/magic-context/transform-context-state.d.ts +5 -2
  36. package/dist/hooks/magic-context/transform-context-state.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/transform.d.ts +1 -0
  39. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  40. package/dist/index.js +1215 -641
  41. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  42. package/dist/shared/transcript.d.ts +2 -2
  43. package/package.json +1 -1
  44. package/src/shared/transcript.ts +2 -2
package/dist/index.js CHANGED
@@ -157573,6 +157573,121 @@ function closeQuietly(db) {
157573
157573
  } catch {}
157574
157574
  }
157575
157575
 
157576
+ // src/features/magic-context/key-files/project-key-files.ts
157577
+ import { createHash as createHash2 } from "node:crypto";
157578
+ import { existsSync as existsSync10, readFileSync as readFileSync9, realpathSync } from "node:fs";
157579
+ import { join as join12, resolve as resolve4, sep } from "node:path";
157580
+ function sha256(input) {
157581
+ return createHash2("sha256").update(input).digest("hex");
157582
+ }
157583
+ function resolveProjectPath(directory) {
157584
+ if (!directory?.trim())
157585
+ return null;
157586
+ try {
157587
+ return realpathSync(directory);
157588
+ } catch {
157589
+ return resolve4(directory);
157590
+ }
157591
+ }
157592
+ function rowToProjectKeyFile(row) {
157593
+ return {
157594
+ projectPath: String(row.project_path),
157595
+ path: String(row.path),
157596
+ content: String(row.content),
157597
+ contentHash: String(row.content_hash),
157598
+ localTokenEstimate: Number(row.local_token_estimate),
157599
+ generatedAt: Number(row.generated_at),
157600
+ generatedByModel: row.generated_by_model == null ? null : String(row.generated_by_model),
157601
+ generationConfigHash: String(row.generation_config_hash),
157602
+ staleReason: row.stale_reason == null ? null : String(row.stale_reason)
157603
+ };
157604
+ }
157605
+ function readCurrentKeyFiles(db, projectPath) {
157606
+ const resolvedProjectPath = resolveProjectPath(projectPath) ?? projectPath;
157607
+ const rows = db.prepare(`SELECT project_path, path, content, content_hash, local_token_estimate,
157608
+ generated_at, generated_by_model, generation_config_hash, stale_reason
157609
+ FROM project_key_files
157610
+ WHERE project_path = ?
157611
+ ORDER BY generated_at DESC, path ASC`).all(resolvedProjectPath);
157612
+ return rows.map(rowToProjectKeyFile);
157613
+ }
157614
+ function getKeyFilesVersion(db, projectPath) {
157615
+ const resolvedProjectPath = resolveProjectPath(projectPath) ?? projectPath;
157616
+ const row = db.prepare("SELECT version FROM project_key_files_version WHERE project_path = ?").get(resolvedProjectPath);
157617
+ return Number(row?.version ?? 0);
157618
+ }
157619
+ function bumpKeyFilesVersion(db, projectPath) {
157620
+ const resolvedProjectPath = resolveProjectPath(projectPath) ?? projectPath;
157621
+ db.prepare(`INSERT INTO project_key_files_version (project_path, version)
157622
+ VALUES (?, 1)
157623
+ ON CONFLICT(project_path) DO UPDATE SET version = version + 1`).run(resolvedProjectPath);
157624
+ return getKeyFilesVersion(db, resolvedProjectPath);
157625
+ }
157626
+ function resolveCommitFiles(projectPath, files) {
157627
+ return files.map((file2) => {
157628
+ try {
157629
+ const disk = readFileSync9(join12(projectPath, file2.path));
157630
+ return {
157631
+ ...file2,
157632
+ contentHash: sha256(disk),
157633
+ staleReason: null
157634
+ };
157635
+ } catch {
157636
+ return {
157637
+ ...file2,
157638
+ contentHash: MISSING_CONTENT_HASH,
157639
+ staleReason: "missing"
157640
+ };
157641
+ }
157642
+ });
157643
+ }
157644
+ function insertResolvedKeyFiles(db, projectPath, files, generatedAt, generatedByModel, generationConfigHash) {
157645
+ const insert = db.prepare(`INSERT INTO project_key_files
157646
+ (project_path, path, content, content_hash, local_token_estimate,
157647
+ generated_at, generated_by_model, generation_config_hash, stale_reason)
157648
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
157649
+ for (const file2 of files) {
157650
+ insert.run(projectPath, file2.path, file2.content, file2.contentHash, file2.localTokenEstimate, generatedAt, generatedByModel, generationConfigHash, file2.staleReason);
157651
+ }
157652
+ }
157653
+ function deleteOrphanProjectKeyFiles(db, now = Date.now()) {
157654
+ const cutoff = now - ORPHAN_GRACE_MS;
157655
+ const rows = db.prepare(`SELECT project_path AS projectPath, MAX(generated_at) AS lastGen
157656
+ FROM project_key_files
157657
+ GROUP BY project_path
157658
+ HAVING lastGen < ?`).all(cutoff);
157659
+ let deletedProjects = 0;
157660
+ for (const row of rows) {
157661
+ if (existsSync10(row.projectPath))
157662
+ continue;
157663
+ try {
157664
+ db.prepare("DELETE FROM project_key_files WHERE project_path = ?").run(row.projectPath);
157665
+ db.prepare("DELETE FROM project_key_files_version WHERE project_path = ?").run(row.projectPath);
157666
+ deletedProjects++;
157667
+ } catch (error51) {
157668
+ log(`[key-files] orphan GC failed for ${row.projectPath}:`, error51);
157669
+ }
157670
+ }
157671
+ return deletedProjects;
157672
+ }
157673
+ function isRelativeProjectFile(projectPath, relativePath) {
157674
+ if (!relativePath || relativePath.startsWith("/") || relativePath.includes(".."))
157675
+ return false;
157676
+ try {
157677
+ const root = realpathSync(projectPath);
157678
+ const absPath = resolve4(projectPath, relativePath);
157679
+ const real = realpathSync(absPath);
157680
+ return real.startsWith(root + sep) || real === root;
157681
+ } catch {
157682
+ return false;
157683
+ }
157684
+ }
157685
+ var MISSING_CONTENT_HASH = "<missing>", ORPHAN_GRACE_MS;
157686
+ var init_project_key_files = __esm(() => {
157687
+ init_logger();
157688
+ ORPHAN_GRACE_MS = 7 * 24 * 60 * 60 * 1000;
157689
+ });
157690
+
157576
157691
  // src/features/magic-context/memory/storage-memory-embeddings.ts
157577
157692
  function isEmbeddingBlob(value) {
157578
157693
  return value instanceof Uint8Array || value instanceof ArrayBuffer;
@@ -157692,13 +157807,13 @@ var init_embedding_cache = __esm(() => {
157692
157807
  });
157693
157808
 
157694
157809
  // src/features/magic-context/memory/normalize-hash.ts
157695
- import { createHash as createHash2 } from "node:crypto";
157810
+ import { createHash as createHash3 } from "node:crypto";
157696
157811
  function normalizeMemoryContent(content) {
157697
157812
  return content.toLowerCase().replace(/\s+/g, " ").trim();
157698
157813
  }
157699
157814
  function computeNormalizedHash(content) {
157700
157815
  const normalized = normalizeMemoryContent(content);
157701
- return createHash2("md5").update(normalized).digest("hex");
157816
+ return createHash3("md5").update(normalized).digest("hex");
157702
157817
  }
157703
157818
  var init_normalize_hash = () => {};
157704
157819
 
@@ -158283,107 +158398,6 @@ function parseUserMemoryRow(row) {
158283
158398
  };
158284
158399
  }
158285
158400
 
158286
- // src/hooks/magic-context/read-session-db.ts
158287
- var exports_read_session_db = {};
158288
- __export(exports_read_session_db, {
158289
- withReadOnlySessionDb: () => withReadOnlySessionDb,
158290
- getRawSessionMessageCountFromDb: () => getRawSessionMessageCountFromDb,
158291
- getMessageTimesFromOpenCodeDb: () => getMessageTimesFromOpenCodeDb,
158292
- findLastAssistantModelFromOpenCodeDb: () => findLastAssistantModelFromOpenCodeDb,
158293
- closeReadOnlySessionDb: () => closeReadOnlySessionDb
158294
- });
158295
- import { join as join11 } from "node:path";
158296
- function getOpenCodeDbPath() {
158297
- return join11(getDataDir(), "opencode", "opencode.db");
158298
- }
158299
- function closeCachedReadOnlyDb() {
158300
- if (!cachedReadOnlyDb) {
158301
- return;
158302
- }
158303
- try {
158304
- closeQuietly(cachedReadOnlyDb.db);
158305
- } catch (error51) {
158306
- log("[magic-context] failed to close cached OpenCode read-only DB:", error51);
158307
- } finally {
158308
- cachedReadOnlyDb = null;
158309
- }
158310
- }
158311
- function getReadOnlySessionDb() {
158312
- const dbPath = getOpenCodeDbPath();
158313
- if (cachedReadOnlyDb?.path === dbPath) {
158314
- return cachedReadOnlyDb.db;
158315
- }
158316
- closeCachedReadOnlyDb();
158317
- const db = new Database(dbPath, { readonly: true });
158318
- cachedReadOnlyDb = { path: dbPath, db };
158319
- return db;
158320
- }
158321
- function withReadOnlySessionDb(fn) {
158322
- return fn(getReadOnlySessionDb());
158323
- }
158324
- function closeReadOnlySessionDb() {
158325
- closeCachedReadOnlyDb();
158326
- }
158327
- function getRawSessionMessageCountFromDb(db, sessionId) {
158328
- const row = db.prepare(`SELECT COUNT(*) as count FROM message WHERE session_id = ?
158329
- AND NOT (COALESCE(json_extract(data, '$.summary'), 0) = 1
158330
- AND COALESCE(json_extract(data, '$.finish'), '') = 'stop')`).get(sessionId);
158331
- return typeof row?.count === "number" ? row.count : 0;
158332
- }
158333
- function getMessageTimesFromOpenCodeDb(sessionId, messageIds) {
158334
- const result = new Map;
158335
- if (messageIds.length === 0)
158336
- return result;
158337
- try {
158338
- withReadOnlySessionDb((db) => {
158339
- const placeholders = messageIds.map(() => "?").join(",");
158340
- const rows = db.prepare(`SELECT id, time_created FROM message WHERE session_id = ? AND id IN (${placeholders})`).all(sessionId, ...messageIds);
158341
- for (const row of rows) {
158342
- if (typeof row.id === "string" && typeof row.time_created === "number") {
158343
- result.set(row.id, row.time_created);
158344
- }
158345
- }
158346
- });
158347
- } catch (error51) {
158348
- log("[magic-context] failed to resolve message times from OpenCode DB:", error51);
158349
- }
158350
- return result;
158351
- }
158352
- function findLastAssistantModelFromOpenCodeDb(sessionId) {
158353
- try {
158354
- return withReadOnlySessionDb((db) => {
158355
- const row = db.prepare(`SELECT json_extract(data, '$.providerID') as providerID,
158356
- json_extract(data, '$.modelID') as modelID,
158357
- json_extract(data, '$.agent') as agent
158358
- FROM message
158359
- WHERE session_id = ?
158360
- AND json_extract(data, '$.role') = 'assistant'
158361
- AND json_extract(data, '$.providerID') IS NOT NULL
158362
- AND json_extract(data, '$.modelID') IS NOT NULL
158363
- ORDER BY time_created DESC
158364
- LIMIT 1`).get(sessionId);
158365
- if (!row || typeof row.providerID !== "string" || typeof row.modelID !== "string") {
158366
- return null;
158367
- }
158368
- const agent = typeof row.agent === "string" && row.agent.length > 0 ? row.agent : undefined;
158369
- return {
158370
- providerID: row.providerID,
158371
- modelID: row.modelID,
158372
- ...agent ? { agent } : {}
158373
- };
158374
- });
158375
- } catch (error51) {
158376
- log("[magic-context] failed to recover live model from OpenCode DB:", error51);
158377
- return null;
158378
- }
158379
- }
158380
- var cachedReadOnlyDb = null;
158381
- var init_read_session_db = __esm(async () => {
158382
- init_data_path();
158383
- init_logger();
158384
- await init_sqlite();
158385
- });
158386
-
158387
158401
  // src/features/magic-context/memory/cosine-similarity.ts
158388
158402
  function cosineSimilarity(a, b) {
158389
158403
  if (a.length !== b.length) {
@@ -158430,7 +158444,7 @@ var init_embedding_identity = __esm(() => {
158430
158444
  // src/features/magic-context/memory/embedding-local.ts
158431
158445
  import { mkdirSync as mkdirSync4 } from "node:fs";
158432
158446
  import { open, stat, unlink, writeFile } from "node:fs/promises";
158433
- import { join as join13 } from "node:path";
158447
+ import { join as join15 } from "node:path";
158434
158448
  async function acquireModelLoadLock(lockPath) {
158435
158449
  const waitStart = Date.now();
158436
158450
  while (true) {
@@ -158467,7 +158481,7 @@ async function acquireModelLoadLock(lockPath) {
158467
158481
  log("[magic-context] embedding-load lock wait exceeded, proceeding without lock");
158468
158482
  return async () => {};
158469
158483
  }
158470
- await new Promise((resolve4) => setTimeout(resolve4, LOCK_POLL_MS));
158484
+ await new Promise((resolve6) => setTimeout(resolve6, LOCK_POLL_MS));
158471
158485
  }
158472
158486
  }
158473
158487
  }
@@ -158564,7 +158578,7 @@ class LocalEmbeddingProvider {
158564
158578
  if (LogLevel && "ERROR" in LogLevel) {
158565
158579
  env.logLevel = LogLevel.ERROR;
158566
158580
  }
158567
- const modelCacheDir = join13(getMagicContextStorageDir(), "models");
158581
+ const modelCacheDir = join15(getMagicContextStorageDir(), "models");
158568
158582
  try {
158569
158583
  mkdirSync4(modelCacheDir, { recursive: true });
158570
158584
  env.cacheDir = modelCacheDir;
@@ -158572,7 +158586,7 @@ class LocalEmbeddingProvider {
158572
158586
  log("[magic-context] could not create model cache dir, using library default");
158573
158587
  }
158574
158588
  const createPipeline = transformersModule.pipeline;
158575
- const lockPath = join13(modelCacheDir, ".load.lock");
158589
+ const lockPath = join15(modelCacheDir, ".load.lock");
158576
158590
  const releaseLock = await acquireModelLoadLock(lockPath);
158577
158591
  const stopHeartbeat = startLockHeartbeat(lockPath);
158578
158592
  try {
@@ -158592,7 +158606,7 @@ class LocalEmbeddingProvider {
158592
158606
  }
158593
158607
  const delayMs = 300 * attempt + Math.floor(Math.random() * 200);
158594
158608
  log(`[magic-context] embedding model load attempt ${attempt}/${MAX_ATTEMPTS} failed transiently, retrying in ${delayMs}ms`);
158595
- await new Promise((resolve4) => setTimeout(resolve4, delayMs));
158609
+ await new Promise((resolve6) => setTimeout(resolve6, delayMs));
158596
158610
  }
158597
158611
  }
158598
158612
  if (this.pipeline) {
@@ -159140,7 +159154,7 @@ var init_storage_memory_fts = __esm(() => {
159140
159154
 
159141
159155
  // src/features/magic-context/memory/project-identity.ts
159142
159156
  import { execSync } from "node:child_process";
159143
- import { createHash as createHash3 } from "node:crypto";
159157
+ import { createHash as createHash4 } from "node:crypto";
159144
159158
  import path4 from "node:path";
159145
159159
  function getRootCommitHash(directory) {
159146
159160
  try {
@@ -159159,7 +159173,7 @@ function getRootCommitHash(directory) {
159159
159173
  }
159160
159174
  function directoryFallback(directory) {
159161
159175
  const canonical = path4.resolve(directory);
159162
- const hash2 = createHash3("md5").update(canonical).digest("hex").slice(0, 12);
159176
+ const hash2 = createHash4("md5").update(canonical).digest("hex").slice(0, 12);
159163
159177
  return `dir:${hash2}`;
159164
159178
  }
159165
159179
  function resolveProjectIdentity(directory) {
@@ -159240,6 +159254,128 @@ var init_compression_depth_storage = __esm(() => {
159240
159254
  clearDepthStatements = new WeakMap;
159241
159255
  });
159242
159256
 
159257
+ // src/hooks/magic-context/read-session-db.ts
159258
+ import { join as join16 } from "node:path";
159259
+ function getOpenCodeDbPath2() {
159260
+ return join16(getDataDir(), "opencode", "opencode.db");
159261
+ }
159262
+ function closeCachedReadOnlyDb() {
159263
+ if (!cachedReadOnlyDb) {
159264
+ return;
159265
+ }
159266
+ try {
159267
+ closeQuietly(cachedReadOnlyDb.db);
159268
+ } catch (error51) {
159269
+ log("[magic-context] failed to close cached OpenCode read-only DB:", error51);
159270
+ } finally {
159271
+ cachedReadOnlyDb = null;
159272
+ }
159273
+ }
159274
+ function getReadOnlySessionDb() {
159275
+ const dbPath = getOpenCodeDbPath2();
159276
+ if (cachedReadOnlyDb?.path === dbPath) {
159277
+ return cachedReadOnlyDb.db;
159278
+ }
159279
+ closeCachedReadOnlyDb();
159280
+ const db = new Database(dbPath, { readonly: true });
159281
+ cachedReadOnlyDb = { path: dbPath, db };
159282
+ return db;
159283
+ }
159284
+ function withReadOnlySessionDb(fn) {
159285
+ return fn(getReadOnlySessionDb());
159286
+ }
159287
+ function getRawSessionMessageCountFromDb(db, sessionId) {
159288
+ const row = db.prepare(`SELECT COUNT(*) as count FROM message WHERE session_id = ?
159289
+ AND NOT (COALESCE(json_extract(data, '$.summary'), 0) = 1
159290
+ AND COALESCE(json_extract(data, '$.finish'), '') = 'stop')`).get(sessionId);
159291
+ return typeof row?.count === "number" ? row.count : 0;
159292
+ }
159293
+ function isMidTurn(_deps, sessionId) {
159294
+ try {
159295
+ return withReadOnlySessionDb((db) => isMidTurnFromOpenCodeDb(db, sessionId));
159296
+ } catch (error51) {
159297
+ log("[magic-context] failed to inspect OpenCode mid-turn state:", error51);
159298
+ return false;
159299
+ }
159300
+ }
159301
+ function isMidTurnFromOpenCodeDb(db, sessionId) {
159302
+ const latestAssistant = db.prepare(`SELECT id,
159303
+ json_extract(data, '$.finish') as finish
159304
+ FROM message
159305
+ WHERE session_id = ?
159306
+ AND json_extract(data, '$.role') = 'assistant'
159307
+ ORDER BY time_created DESC
159308
+ LIMIT 1`).get(sessionId);
159309
+ if (typeof latestAssistant?.id !== "string")
159310
+ return false;
159311
+ if (latestAssistant.finish === "tool-calls")
159312
+ return true;
159313
+ const partRows = db.prepare("SELECT data FROM part WHERE session_id = ? AND message_id = ?").all(sessionId, latestAssistant.id);
159314
+ return partRows.some((row) => {
159315
+ if (typeof row.data !== "string" || row.data.length === 0)
159316
+ return false;
159317
+ try {
159318
+ const part = JSON.parse(row.data);
159319
+ return part.type === "tool" && part.providerExecuted !== true;
159320
+ } catch {
159321
+ return false;
159322
+ }
159323
+ });
159324
+ }
159325
+ function getMessageTimesFromOpenCodeDb(sessionId, messageIds) {
159326
+ const result = new Map;
159327
+ if (messageIds.length === 0)
159328
+ return result;
159329
+ try {
159330
+ withReadOnlySessionDb((db) => {
159331
+ const placeholders = messageIds.map(() => "?").join(",");
159332
+ const rows = db.prepare(`SELECT id, time_created FROM message WHERE session_id = ? AND id IN (${placeholders})`).all(sessionId, ...messageIds);
159333
+ for (const row of rows) {
159334
+ if (typeof row.id === "string" && typeof row.time_created === "number") {
159335
+ result.set(row.id, row.time_created);
159336
+ }
159337
+ }
159338
+ });
159339
+ } catch (error51) {
159340
+ log("[magic-context] failed to resolve message times from OpenCode DB:", error51);
159341
+ }
159342
+ return result;
159343
+ }
159344
+ function findLastAssistantModelFromOpenCodeDb(sessionId) {
159345
+ try {
159346
+ return withReadOnlySessionDb((db) => {
159347
+ const row = db.prepare(`SELECT json_extract(data, '$.providerID') as providerID,
159348
+ json_extract(data, '$.modelID') as modelID,
159349
+ json_extract(data, '$.agent') as agent
159350
+ FROM message
159351
+ WHERE session_id = ?
159352
+ AND json_extract(data, '$.role') = 'assistant'
159353
+ AND json_extract(data, '$.providerID') IS NOT NULL
159354
+ AND json_extract(data, '$.modelID') IS NOT NULL
159355
+ ORDER BY time_created DESC
159356
+ LIMIT 1`).get(sessionId);
159357
+ if (!row || typeof row.providerID !== "string" || typeof row.modelID !== "string") {
159358
+ return null;
159359
+ }
159360
+ const agent = typeof row.agent === "string" && row.agent.length > 0 ? row.agent : undefined;
159361
+ return {
159362
+ providerID: row.providerID,
159363
+ modelID: row.modelID,
159364
+ ...agent ? { agent } : {}
159365
+ };
159366
+ });
159367
+ } catch (error51) {
159368
+ log("[magic-context] failed to recover live model from OpenCode DB:", error51);
159369
+ return null;
159370
+ }
159371
+ }
159372
+ var cachedReadOnlyDb = null;
159373
+ var init_read_session_db = __esm(async () => {
159374
+ init_data_path();
159375
+ init_logger();
159376
+ await init_sqlite();
159377
+ });
159378
+
159243
159379
  // src/hooks/magic-context/read-session-raw.ts
159244
159380
  function isRawMessageRow(row) {
159245
159381
  if (row === null || typeof row !== "object")
@@ -160374,15 +160510,55 @@ var init_migrations = __esm(async () => {
160374
160510
  db.exec("ALTER TABLE session_meta ADD COLUMN pending_compaction_marker_state TEXT");
160375
160511
  }
160376
160512
  }
160513
+ },
160514
+ {
160515
+ version: 14,
160516
+ description: "Add project-scoped key files and version counter",
160517
+ up: (db) => {
160518
+ db.exec(`
160519
+ CREATE TABLE IF NOT EXISTS project_key_files (
160520
+ project_path TEXT NOT NULL,
160521
+ path TEXT NOT NULL,
160522
+ content TEXT NOT NULL,
160523
+ content_hash TEXT NOT NULL,
160524
+ local_token_estimate INTEGER NOT NULL,
160525
+ generated_at INTEGER NOT NULL,
160526
+ generated_by_model TEXT,
160527
+ generation_config_hash TEXT NOT NULL,
160528
+ stale_reason TEXT,
160529
+ PRIMARY KEY (project_path, path)
160530
+ );
160531
+
160532
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_project
160533
+ ON project_key_files(project_path);
160534
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_generated_at
160535
+ ON project_key_files(project_path, generated_at);
160536
+
160537
+ CREATE TABLE IF NOT EXISTS project_key_files_version (
160538
+ project_path TEXT PRIMARY KEY,
160539
+ version INTEGER NOT NULL DEFAULT 0
160540
+ );
160541
+ `);
160542
+ }
160543
+ },
160544
+ {
160545
+ version: 15,
160546
+ description: "Add deferred_execute_state column for boundary execution drain",
160547
+ up: (db) => {
160548
+ const cols = db.prepare("PRAGMA table_info(session_meta)").all();
160549
+ if (!cols.some((c) => c.name === "deferred_execute_state")) {
160550
+ db.exec("ALTER TABLE session_meta ADD COLUMN deferred_execute_state TEXT");
160551
+ }
160552
+ }
160377
160553
  }
160378
160554
  ];
160379
160555
  });
160380
160556
 
160381
160557
  // src/features/magic-context/tool-owner-backfill.ts
160382
- import { existsSync as existsSync10 } from "node:fs";
160383
- import { join as join14 } from "node:path";
160558
+ import { existsSync as existsSync12 } from "node:fs";
160559
+ import { join as join17 } from "node:path";
160384
160560
  function resolveOpencodeDbPath() {
160385
- return join14(getDataDir(), "opencode", "opencode.db");
160561
+ return join17(getDataDir(), "opencode", "opencode.db");
160386
160562
  }
160387
160563
  function ensureBackfillStateTable(db) {
160388
160564
  db.exec(`
@@ -160417,7 +160593,7 @@ function runToolOwnerBackfill(db) {
160417
160593
  return result;
160418
160594
  }
160419
160595
  const opencodeDbPath = resolveOpencodeDbPath();
160420
- if (!existsSync10(opencodeDbPath)) {
160596
+ if (!existsSync12(opencodeDbPath)) {
160421
160597
  log(`[backfill] OpenCode DB not found at ${opencodeDbPath} — marking all unbackfilled sessions as skipped. Lazy adoption (defense-in-depth) handles legacy rows at runtime.`);
160422
160598
  markAllUnbackfilledSessionsSkipped(db);
160423
160599
  result.sessionsSkippedNoOcDb = countSessionsByStatus(db, "skipped");
@@ -160627,25 +160803,25 @@ var init_tool_owner_backfill = __esm(() => {
160627
160803
  });
160628
160804
 
160629
160805
  // src/features/magic-context/storage-db.ts
160630
- import { copyFileSync, cpSync, existsSync as existsSync11, mkdirSync as mkdirSync5 } from "node:fs";
160631
- import { join as join15 } from "node:path";
160806
+ import { copyFileSync, cpSync, existsSync as existsSync13, mkdirSync as mkdirSync5 } from "node:fs";
160807
+ import { join as join18 } from "node:path";
160632
160808
  function resolveDatabasePath() {
160633
160809
  const dbDir = getMagicContextStorageDir();
160634
- return { dbDir, dbPath: join15(dbDir, "context.db") };
160810
+ return { dbDir, dbPath: join18(dbDir, "context.db") };
160635
160811
  }
160636
160812
  function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
160637
- if (existsSync11(targetDbPath))
160813
+ if (existsSync13(targetDbPath))
160638
160814
  return;
160639
160815
  const legacyDir = getLegacyOpenCodeMagicContextStorageDir();
160640
- const legacyDbPath = join15(legacyDir, "context.db");
160641
- if (!existsSync11(legacyDbPath))
160816
+ const legacyDbPath = join18(legacyDir, "context.db");
160817
+ if (!existsSync13(legacyDbPath))
160642
160818
  return;
160643
160819
  log(`[magic-context] migrating legacy plugin storage: ${legacyDir} -> ${targetDbDir} (legacy left in place as backup)`);
160644
160820
  mkdirSync5(targetDbDir, { recursive: true });
160645
160821
  for (const suffix of ["", "-wal", "-shm"]) {
160646
160822
  const src = `${legacyDbPath}${suffix}`;
160647
- const dst = join15(targetDbDir, `context.db${suffix}`);
160648
- if (existsSync11(src)) {
160823
+ const dst = join18(targetDbDir, `context.db${suffix}`);
160824
+ if (existsSync13(src)) {
160649
160825
  try {
160650
160826
  copyFileSync(src, dst);
160651
160827
  } catch (error51) {
@@ -160653,9 +160829,9 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
160653
160829
  }
160654
160830
  }
160655
160831
  }
160656
- const legacyModelsDir = join15(legacyDir, "models");
160657
- const targetModelsDir = join15(targetDbDir, "models");
160658
- if (existsSync11(legacyModelsDir) && !existsSync11(targetModelsDir)) {
160832
+ const legacyModelsDir = join18(legacyDir, "models");
160833
+ const targetModelsDir = join18(targetDbDir, "models");
160834
+ if (existsSync13(legacyModelsDir) && !existsSync13(targetModelsDir)) {
160659
160835
  try {
160660
160836
  cpSync(legacyModelsDir, targetModelsDir, { recursive: true });
160661
160837
  } catch (error51) {
@@ -160803,6 +160979,26 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
160803
160979
  );
160804
160980
  CREATE INDEX IF NOT EXISTS idx_dream_runs_project ON dream_runs(project_path, finished_at DESC);
160805
160981
 
160982
+ CREATE TABLE IF NOT EXISTS project_key_files (
160983
+ project_path TEXT NOT NULL,
160984
+ path TEXT NOT NULL,
160985
+ content TEXT NOT NULL,
160986
+ content_hash TEXT NOT NULL,
160987
+ local_token_estimate INTEGER NOT NULL,
160988
+ generated_at INTEGER NOT NULL,
160989
+ generated_by_model TEXT,
160990
+ generation_config_hash TEXT NOT NULL,
160991
+ stale_reason TEXT,
160992
+ PRIMARY KEY (project_path, path)
160993
+ );
160994
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_project ON project_key_files(project_path);
160995
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_generated_at ON project_key_files(project_path, generated_at);
160996
+
160997
+ CREATE TABLE IF NOT EXISTS project_key_files_version (
160998
+ project_path TEXT PRIMARY KEY,
160999
+ version INTEGER NOT NULL DEFAULT 0
161000
+ );
161001
+
160806
161002
  -- (smart_notes: see note above; merged into unified notes table by migration v1)
160807
161003
 
160808
161004
  CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
@@ -160880,7 +161076,11 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
160880
161076
  -- valid JSON blob written via setPendingCompactionMarkerState.
160881
161077
  -- Excluded from healNullTextColumns. Readers filter IS NOT NULL AND
160882
161078
  -- != empty-string defensively. Plan v6 section 3.
160883
- pending_compaction_marker_state TEXT
161079
+ pending_compaction_marker_state TEXT,
161080
+ -- deferred_execute_state: intentionally NULLABLE without a default.
161081
+ -- Absence is SQL NULL; presence is a JSON blob written via
161082
+ -- setDeferredExecutePendingIfAbsent. Excluded from healNullTextColumns.
161083
+ deferred_execute_state TEXT
160884
161084
  );
160885
161085
 
160886
161086
  CREATE INDEX IF NOT EXISTS idx_tags_session_tag_number ON tags(session_id, tag_number);
@@ -160969,6 +161169,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
160969
161169
  ensureColumn(db, "session_meta", "detected_context_limit", "INTEGER DEFAULT 0");
160970
161170
  ensureColumn(db, "session_meta", "needs_emergency_recovery", "INTEGER DEFAULT 0");
160971
161171
  ensureColumn(db, "session_meta", "pending_compaction_marker_state", "TEXT");
161172
+ ensureColumn(db, "session_meta", "deferred_execute_state", "TEXT");
160972
161173
  ensureColumn(db, "tags", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
160973
161174
  ensureColumn(db, "pending_ops", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
160974
161175
  ensureColumn(db, "source_contents", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
@@ -161045,7 +161246,15 @@ function ensureColumn(db, table, column, definition) {
161045
161246
  if (rows.some((row) => row.name === column)) {
161046
161247
  return;
161047
161248
  }
161048
- db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
161249
+ try {
161250
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
161251
+ } catch (err) {
161252
+ const recheck = db.prepare(`PRAGMA table_info(${table})`).all();
161253
+ if (recheck.some((row) => row.name === column)) {
161254
+ return;
161255
+ }
161256
+ throw err;
161257
+ }
161049
161258
  }
161050
161259
  function openDatabase() {
161051
161260
  const { dbDir, dbPath } = resolveDatabasePath();
@@ -161062,6 +161271,11 @@ function openDatabase() {
161062
161271
  const db = new Database(dbPath);
161063
161272
  initializeDatabase(db);
161064
161273
  runMigrations(db);
161274
+ try {
161275
+ deleteOrphanProjectKeyFiles(db);
161276
+ } catch (error51) {
161277
+ log(`[magic-context] key-files orphan GC failed: ${getErrorMessage(error51)}`);
161278
+ }
161065
161279
  try {
161066
161280
  runToolOwnerBackfill(db);
161067
161281
  } catch (error51) {
@@ -161089,6 +161303,7 @@ var databases, persistenceByDatabase, persistenceErrorByDatabase;
161089
161303
  var init_storage_db = __esm(async () => {
161090
161304
  init_data_path();
161091
161305
  init_logger();
161306
+ init_project_key_files();
161092
161307
  init_tool_definition_tokens();
161093
161308
  init_tool_owner_backfill();
161094
161309
  await __promiseAll([
@@ -161526,6 +161741,30 @@ function clearPendingCompactionMarkerStateIf(db, sessionId, expected) {
161526
161741
  WHERE session_id = ? AND pending_compaction_marker_state = ?`).run(sessionId, expectedBlob);
161527
161742
  return result.changes > 0;
161528
161743
  }
161744
+ function peekDeferredExecutePending(db, sessionId) {
161745
+ const row = db.prepare("SELECT deferred_execute_state FROM session_meta WHERE session_id = ?").get(sessionId);
161746
+ const raw = row?.deferred_execute_state;
161747
+ if (raw === null || raw === undefined || raw === "")
161748
+ return null;
161749
+ try {
161750
+ return JSON.parse(raw);
161751
+ } catch {
161752
+ return null;
161753
+ }
161754
+ }
161755
+ function setDeferredExecutePendingIfAbsent(db, sessionId, payload) {
161756
+ ensureSessionMetaRow(db, sessionId);
161757
+ const payloadBlob = stableStringify(payload);
161758
+ const result = db.prepare(`UPDATE session_meta SET deferred_execute_state = ?
161759
+ WHERE session_id = ? AND deferred_execute_state IS NULL`).run(payloadBlob, sessionId);
161760
+ return result.changes > 0;
161761
+ }
161762
+ function clearDeferredExecutePendingIfMatches(db, sessionId, expected) {
161763
+ const expectedBlob = stableStringify(expected);
161764
+ const result = db.prepare(`UPDATE session_meta SET deferred_execute_state = NULL
161765
+ WHERE session_id = ? AND deferred_execute_state = ?`).run(sessionId, expectedBlob);
161766
+ return result.changes > 0;
161767
+ }
161529
161768
  function getSessionsWithPendingMarker(db) {
161530
161769
  const rows = db.prepare(`SELECT session_id FROM session_meta
161531
161770
  WHERE pending_compaction_marker_state IS NOT NULL
@@ -162059,12 +162298,12 @@ var init_storage = __esm(async () => {
162059
162298
  });
162060
162299
 
162061
162300
  // src/shared/models-dev-cache.ts
162062
- import { createHash as createHash4 } from "node:crypto";
162063
- import { existsSync as existsSync12, readFileSync as readFileSync8 } from "node:fs";
162064
- import { homedir as homedir8, platform as platform3 } from "node:os";
162065
- import { join as join16 } from "node:path";
162301
+ import { createHash as createHash5 } from "node:crypto";
162302
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "node:fs";
162303
+ import { homedir as homedir9, platform as platform3 } from "node:os";
162304
+ import { join as join19 } from "node:path";
162066
162305
  function hashFast(input) {
162067
- return createHash4("sha1").update(input).digest("hex");
162306
+ return createHash5("sha1").update(input).digest("hex");
162068
162307
  }
162069
162308
  function getModelsJsonPath() {
162070
162309
  const explicit = process.env.OPENCODE_MODELS_PATH?.trim();
@@ -162073,16 +162312,16 @@ function getModelsJsonPath() {
162073
162312
  const cacheBase = getCacheDir();
162074
162313
  const source = process.env.OPENCODE_MODELS_URL?.trim();
162075
162314
  const filename = source && source !== "https://models.dev" ? `models-${hashFast(source)}.json` : "models.json";
162076
- return join16(cacheBase, "opencode", filename);
162315
+ return join19(cacheBase, "opencode", filename);
162077
162316
  }
162078
162317
  function getOpencodeConfigPath() {
162079
162318
  const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
162080
- const configDir = envDir ? envDir : platform3() === "win32" ? join16(homedir8(), ".config", "opencode") : join16(process.env.XDG_CONFIG_HOME || join16(homedir8(), ".config"), "opencode");
162081
- const jsonc = join16(configDir, "opencode.jsonc");
162082
- if (existsSync12(jsonc))
162319
+ const configDir = envDir ? envDir : platform3() === "win32" ? join19(homedir9(), ".config", "opencode") : join19(process.env.XDG_CONFIG_HOME || join19(homedir9(), ".config"), "opencode");
162320
+ const jsonc = join19(configDir, "opencode.jsonc");
162321
+ if (existsSync14(jsonc))
162083
162322
  return jsonc;
162084
- const json2 = join16(configDir, "opencode.json");
162085
- if (existsSync12(json2))
162323
+ const json2 = join19(configDir, "opencode.json");
162324
+ if (existsSync14(json2))
162086
162325
  return json2;
162087
162326
  return null;
162088
162327
  }
@@ -162114,9 +162353,9 @@ function loadModelsDevMetadataFromFile() {
162114
162353
  const modelsJsonPath = getModelsJsonPath();
162115
162354
  let fileFound = false;
162116
162355
  try {
162117
- if (existsSync12(modelsJsonPath)) {
162356
+ if (existsSync14(modelsJsonPath)) {
162118
162357
  fileFound = true;
162119
- const raw = readFileSync8(modelsJsonPath, "utf-8");
162358
+ const raw = readFileSync11(modelsJsonPath, "utf-8");
162120
162359
  const data = JSON.parse(raw);
162121
162360
  for (const [providerId, provider2] of Object.entries(data)) {
162122
162361
  if (!provider2?.models || typeof provider2.models !== "object")
@@ -162131,8 +162370,8 @@ function loadModelsDevMetadataFromFile() {
162131
162370
  }
162132
162371
  try {
162133
162372
  const configPath = getOpencodeConfigPath();
162134
- if (configPath && existsSync12(configPath)) {
162135
- const config2 = parseJsonc(readFileSync8(configPath, "utf-8"));
162373
+ if (configPath && existsSync14(configPath)) {
162374
+ const config2 = parseJsonc(readFileSync11(configPath, "utf-8"));
162136
162375
  if (config2.provider && typeof config2.provider === "object") {
162137
162376
  for (const [providerId, provider2] of Object.entries(config2.provider)) {
162138
162377
  if (!provider2?.models || typeof provider2.models !== "object")
@@ -162239,7 +162478,7 @@ var init_rpc_notifications = __esm(() => {
162239
162478
  });
162240
162479
 
162241
162480
  // src/features/magic-context/compaction-marker.ts
162242
- import { join as join17 } from "node:path";
162481
+ import { join as join20 } from "node:path";
162243
162482
  function randomBase62(length) {
162244
162483
  const chars = [];
162245
162484
  for (let i = 0;i < length; i++) {
@@ -162259,7 +162498,7 @@ function generatePartId(timestampMs, counter = 0n) {
162259
162498
  return generateId("prt", timestampMs, counter);
162260
162499
  }
162261
162500
  function getOpenCodeDbPath3() {
162262
- return join17(getDataDir(), "opencode", "opencode.db");
162501
+ return join20(getDataDir(), "opencode", "opencode.db");
162263
162502
  }
162264
162503
  function isOpenCodeSchemaCompatible(db, dbPath) {
162265
162504
  if (cachedSchemaCompatible?.path === dbPath) {
@@ -162401,7 +162640,7 @@ var init_compaction_marker = __esm(async () => {
162401
162640
  });
162402
162641
 
162403
162642
  // src/hooks/magic-context/compaction-marker-manager.ts
162404
- import { join as join18 } from "node:path";
162643
+ import { join as join21 } from "node:path";
162405
162644
  function validatePendingTarget(db, sessionId, pending) {
162406
162645
  const ocMessage = getOpenCodeMessageById(sessionId, pending.endMessageId);
162407
162646
  if (!ocMessage) {
@@ -162508,7 +162747,7 @@ function removeCompactionMarkerForSession(db, sessionId) {
162508
162747
  }
162509
162748
  }
162510
162749
  function checkCompactionMarkerConsistency(db) {
162511
- const opencodeDbPath = join18(getDataDir(), "opencode", "opencode.db");
162750
+ const opencodeDbPath = join21(getDataDir(), "opencode", "opencode.db");
162512
162751
  let opencodeDb;
162513
162752
  try {
162514
162753
  opencodeDb = new Database(opencodeDbPath, { readonly: true });
@@ -162792,7 +163031,7 @@ var init_compartment_runner_validation = __esm(async () => {
162792
163031
 
162793
163032
  // src/hooks/magic-context/compartment-runner-historian.ts
162794
163033
  import { mkdirSync as mkdirSync6, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
162795
- import { join as join19 } from "node:path";
163034
+ import { join as join22 } from "node:path";
162796
163035
  function historianResponseDumpDir(directory) {
162797
163036
  return getProjectMagicContextHistorianDir(directory);
162798
163037
  }
@@ -162957,7 +163196,7 @@ async function runHistorianPrompt(args) {
162957
163196
  };
162958
163197
  } finally {
162959
163198
  if (agentSessionId) {
162960
- await client.session.delete({ path: { id: agentSessionId }, query: { directory: sessionDirectory } }).catch((e) => {
163199
+ await client.session.delete({ path: { id: agentSessionId } }).catch((e) => {
162961
163200
  sessionLog(parentSessionId, "compartment agent: session cleanup failed", getErrorMessage(e));
162962
163201
  });
162963
163202
  }
@@ -163022,8 +163261,8 @@ function isTransientHistorianPromptError(message) {
163022
163261
  ].some((token) => normalized.includes(token));
163023
163262
  }
163024
163263
  function sleep(ms) {
163025
- return new Promise((resolve4) => {
163026
- setTimeout(resolve4, ms);
163264
+ return new Promise((resolve6) => {
163265
+ setTimeout(resolve6, ms);
163027
163266
  });
163028
163267
  }
163029
163268
  function cleanupHistorianDump(sessionId, dumpPath) {
@@ -163044,7 +163283,7 @@ function dumpHistorianResponse(sessionId, directory, label, text) {
163044
163283
  mkdirSync6(dumpDir, { recursive: true });
163045
163284
  const safeSessionId = sanitizeDumpName(sessionId);
163046
163285
  const safeLabel = sanitizeDumpName(label);
163047
- const dumpPath = join19(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
163286
+ const dumpPath = join22(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
163048
163287
  writeFileSync5(dumpPath, text, "utf8");
163049
163288
  sessionLog(sessionId, "compartment agent: historian response dumped", {
163050
163289
  label,
@@ -164401,14 +164640,14 @@ var init_caveman = __esm(() => {
164401
164640
 
164402
164641
  // src/hooks/magic-context/historian-state-file.ts
164403
164642
  import { mkdirSync as mkdirSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "node:fs";
164404
- import { join as join20 } from "node:path";
164643
+ import { join as join23 } from "node:path";
164405
164644
  function maybeWriteHistorianStateFile(sessionId, existingState, directory) {
164406
164645
  if (existingState.length <= HISTORIAN_STATE_INLINE_THRESHOLD)
164407
164646
  return;
164408
164647
  try {
164409
164648
  const dir = getProjectMagicContextHistorianDir(directory);
164410
164649
  mkdirSync7(dir, { recursive: true });
164411
- const path5 = join20(dir, `state-${sessionId}-${Date.now()}.xml`);
164650
+ const path5 = join23(dir, `state-${sessionId}-${Date.now()}.xml`);
164412
164651
  writeFileSync6(path5, existingState, "utf8");
164413
164652
  return path5;
164414
164653
  } catch {
@@ -164911,7 +165150,7 @@ async function runCompressorPass(args) {
164911
165150
  return null;
164912
165151
  } finally {
164913
165152
  if (agentSessionId) {
164914
- await client.session.delete({ path: { id: agentSessionId }, query: { directory } }).catch((e) => {
165153
+ await client.session.delete({ path: { id: agentSessionId } }).catch((e) => {
164915
165154
  sessionLog(sessionId, "compressor: session cleanup failed:", getErrorMessage(e));
164916
165155
  });
164917
165156
  }
@@ -165566,8 +165805,8 @@ var exports_tui_config = {};
165566
165805
  __export(exports_tui_config, {
165567
165806
  ensureTuiPluginEntry: () => ensureTuiPluginEntry
165568
165807
  });
165569
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "node:fs";
165570
- import { dirname as dirname8, join as join23 } from "node:path";
165808
+ import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
165809
+ import { dirname as dirname8, join as join27 } from "node:path";
165571
165810
  function isMagicContextEntry(entry) {
165572
165811
  if (!entry)
165573
165812
  return false;
@@ -165581,11 +165820,11 @@ function isMagicContextEntry(entry) {
165581
165820
  }
165582
165821
  function resolveTuiConfigPath() {
165583
165822
  const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
165584
- const jsoncPath = join23(configDir, "tui.jsonc");
165585
- const jsonPath = join23(configDir, "tui.json");
165586
- if (existsSync14(jsoncPath))
165823
+ const jsoncPath = join27(configDir, "tui.jsonc");
165824
+ const jsonPath = join27(configDir, "tui.json");
165825
+ if (existsSync16(jsoncPath))
165587
165826
  return jsoncPath;
165588
- if (existsSync14(jsonPath))
165827
+ if (existsSync16(jsonPath))
165589
165828
  return jsonPath;
165590
165829
  return jsonPath;
165591
165830
  }
@@ -165593,9 +165832,9 @@ function ensureTuiPluginEntry() {
165593
165832
  try {
165594
165833
  const configPath = resolveTuiConfigPath();
165595
165834
  let config2 = {};
165596
- if (existsSync14(configPath)) {
165597
- const raw = readFileSync11(configPath, "utf-8");
165598
- config2 = import_comment_json3.parse(raw) ?? {};
165835
+ if (existsSync16(configPath)) {
165836
+ const raw = readFileSync15(configPath, "utf-8");
165837
+ config2 = import_comment_json4.parse(raw) ?? {};
165599
165838
  }
165600
165839
  const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((p) => typeof p === "string") : [];
165601
165840
  const existingIdx = plugins.findIndex(isMagicContextEntry);
@@ -165614,7 +165853,7 @@ function ensureTuiPluginEntry() {
165614
165853
  }
165615
165854
  config2.plugin = plugins;
165616
165855
  mkdirSync9(dirname8(configPath), { recursive: true });
165617
- writeFileSync8(configPath, `${import_comment_json3.stringify(config2, null, 2)}
165856
+ writeFileSync8(configPath, `${import_comment_json4.stringify(config2, null, 2)}
165618
165857
  `);
165619
165858
  log(`[magic-context] updated TUI plugin entry in ${configPath}`);
165620
165859
  return true;
@@ -165623,11 +165862,11 @@ function ensureTuiPluginEntry() {
165623
165862
  return false;
165624
165863
  }
165625
165864
  }
165626
- var import_comment_json3, PLUGIN_NAME = "@cortexkit/opencode-magic-context", PLUGIN_ENTRY;
165865
+ var import_comment_json4, PLUGIN_NAME = "@cortexkit/opencode-magic-context", PLUGIN_ENTRY;
165627
165866
  var init_tui_config = __esm(() => {
165628
165867
  init_logger();
165629
165868
  init_opencode_config_dir();
165630
- import_comment_json3 = __toESM(require_src2(), 1);
165869
+ import_comment_json4 = __toESM(require_src2(), 1);
165631
165870
  PLUGIN_ENTRY = `${PLUGIN_NAME}@latest`;
165632
165871
  });
165633
165872
  // src/config/index.ts
@@ -166377,8 +166616,7 @@ async function runSidekick(deps) {
166377
166616
  } finally {
166378
166617
  if (agentSessionId) {
166379
166618
  await deps.client.session.delete({
166380
- path: { id: agentSessionId },
166381
- query: { directory: deps.sessionDirectory ?? deps.projectPath }
166619
+ path: { id: agentSessionId }
166382
166620
  }).catch((error51) => {
166383
166621
  log("[magic-context] failed to delete sidekick child session:", error51);
166384
166622
  });
@@ -166735,8 +166973,17 @@ function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackage
166735
166973
  if (nodeModulesDir && basename(nodeModulesDir) === "node_modules") {
166736
166974
  const installDir = dirname4(nodeModulesDir);
166737
166975
  const packageJsonPath = join5(installDir, "package.json");
166738
- if (existsSync5(packageJsonPath))
166739
- return { installDir, packageJsonPath };
166976
+ if (!existsSync5(packageJsonPath)) {
166977
+ try {
166978
+ writeFileSync2(packageJsonPath, `${JSON.stringify({ private: true, dependencies: {} }, null, 2)}
166979
+ `);
166980
+ log(`[auto-update-checker] Seeded missing package.json at ${packageJsonPath} (issue #73)`);
166981
+ } catch (err) {
166982
+ warn2(`[auto-update-checker] Could not seed package.json at ${packageJsonPath}: ${String(err)}`);
166983
+ return null;
166984
+ }
166985
+ }
166986
+ return { installDir, packageJsonPath };
166740
166987
  }
166741
166988
  return null;
166742
166989
  }
@@ -167038,6 +167285,16 @@ function isLeaseActive(db) {
167038
167285
  function getLeaseHolder(db) {
167039
167286
  return getDreamState(db, LEASE_HOLDER_KEY);
167040
167287
  }
167288
+ function peekLeaseHolderAndExpiry(db, expectedHolder) {
167289
+ const holder = getDreamState(db, LEASE_HOLDER_KEY);
167290
+ if (holder !== expectedHolder)
167291
+ return false;
167292
+ const expiryStr = getDreamState(db, LEASE_EXPIRY_KEY);
167293
+ if (!expiryStr)
167294
+ return false;
167295
+ const expiry = Number(expiryStr);
167296
+ return Number.isFinite(expiry) && expiry >= Date.now();
167297
+ }
167041
167298
  function acquireLease(db, holderId) {
167042
167299
  return db.transaction(() => {
167043
167300
  if (isLeaseActive(db)) {
@@ -167151,192 +167408,530 @@ init_assistant_message_extractor();
167151
167408
  init_data_path();
167152
167409
  init_logger();
167153
167410
  await init_sqlite();
167154
- import { existsSync as existsSync9 } from "node:fs";
167155
- import { join as join12 } from "node:path";
167411
+ import { existsSync as existsSync11 } from "node:fs";
167412
+ import { join as join14 } from "node:path";
167156
167413
 
167157
167414
  // src/features/magic-context/key-files/identify-key-files.ts
167415
+ init_read_session_formatting();
167416
+ init_shared();
167417
+ init_assistant_message_extractor();
167158
167418
  init_logger();
167419
+ import { readFileSync as readFileSync10 } from "node:fs";
167420
+ import { join as join13 } from "node:path";
167159
167421
 
167160
- // src/features/magic-context/key-files/read-stats.ts
167161
- function getSessionReadStats(openCodeDb, sessionId, minReads) {
167162
- const fullReads = openCodeDb.prepare(`
167163
- WITH full_reads AS (
167164
- SELECT
167165
- json_extract(json_extract(data, '$.state'), '$.input.filePath') as file_path,
167166
- LENGTH(json_extract(json_extract(data, '$.state'), '$.output')) as output_bytes,
167167
- p.time_created,
167168
- ROW_NUMBER() OVER (
167169
- PARTITION BY json_extract(json_extract(data, '$.state'), '$.input.filePath')
167170
- ORDER BY p.time_created DESC
167171
- ) as rn
167172
- FROM part p
167173
- WHERE p.session_id = ?
167174
- AND json_extract(data, '$.type') = 'tool'
167175
- AND json_extract(data, '$.tool') = 'read'
167176
- AND json_extract(json_extract(data, '$.state'), '$.input.filePath') IS NOT NULL
167177
- AND json_extract(json_extract(data, '$.state'), '$.input.startLine') IS NULL
167178
- AND json_extract(json_extract(data, '$.state'), '$.input.start_line') IS NULL
167179
- AND json_extract(json_extract(data, '$.state'), '$.input.endLine') IS NULL
167180
- AND json_extract(json_extract(data, '$.state'), '$.input.end_line') IS NULL
167181
- AND json_extract(json_extract(data, '$.state'), '$.input.offset') IS NULL
167182
- AND json_extract(json_extract(data, '$.state'), '$.input.limit') IS NULL
167183
- ),
167184
- file_counts AS (
167185
- SELECT file_path, COUNT(*) as full_read_count
167186
- FROM full_reads
167187
- GROUP BY file_path
167188
- HAVING full_read_count >= ?
167189
- )
167190
- SELECT
167191
- r.file_path,
167192
- fc.full_read_count,
167193
- r.output_bytes as latest_read_bytes
167194
- FROM full_reads r
167195
- JOIN file_counts fc ON r.file_path = fc.file_path
167196
- WHERE r.rn = 1
167197
- ORDER BY fc.full_read_count DESC
167198
- `).all(sessionId, minReads);
167199
- if (fullReads.length === 0)
167200
- return [];
167201
- const editCounts = new Map;
167202
- const editRows = openCodeDb.prepare(`
167203
- SELECT
167204
- json_extract(json_extract(data, '$.state'), '$.input.filePath') as file_path,
167205
- COUNT(*) as edit_count
167206
- FROM part p
167207
- WHERE p.session_id = ?
167208
- AND json_extract(data, '$.type') = 'tool'
167209
- AND json_extract(data, '$.tool') IN ('edit', 'write', 'mcp_edit', 'mcp_write')
167210
- AND json_extract(json_extract(data, '$.state'), '$.input.filePath') IS NOT NULL
167211
- GROUP BY file_path
167212
- `).all(sessionId);
167213
- for (const row of editRows) {
167214
- editCounts.set(row.file_path, row.edit_count);
167215
- }
167216
- return fullReads.map((row) => ({
167217
- filePath: row.file_path,
167218
- fullReadCount: row.full_read_count,
167219
- spreadAcrossCompartments: 0,
167220
- editCount: editCounts.get(row.file_path) ?? 0,
167221
- latestReadBytes: row.latest_read_bytes ?? 0,
167222
- latestReadTokens: Math.ceil((row.latest_read_bytes ?? 0) / 3.5)
167223
- }));
167422
+ // src/features/magic-context/key-files/aft-availability.ts
167423
+ var import_comment_json3 = __toESM(require_src2(), 1);
167424
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
167425
+ import { homedir as homedir8 } from "node:os";
167426
+ import { join as join11 } from "node:path";
167427
+ var overrideAvailability = null;
167428
+ function parseConfig(path4) {
167429
+ if (!existsSync9(path4))
167430
+ return null;
167431
+ return import_comment_json3.parse(readFileSync8(path4, "utf-8"));
167432
+ }
167433
+ function entryMatchesAft(entry) {
167434
+ const value = Array.isArray(entry) ? entry[0] : entry;
167435
+ return typeof value === "string" && (value.includes("@cortexkit/aft") || value.includes("aft-opencode") || value.includes("aft-pi"));
167436
+ }
167437
+ function hasAftInArray(value) {
167438
+ return Array.isArray(value) && value.some(entryMatchesAft);
167439
+ }
167440
+ function hasAftAtKeys(value, keys) {
167441
+ if (!value || typeof value !== "object")
167442
+ return false;
167443
+ const record2 = value;
167444
+ for (const key of keys) {
167445
+ if (hasAftInArray(record2[key]))
167446
+ return true;
167447
+ }
167448
+ return false;
167449
+ }
167450
+ function getAftAvailability() {
167451
+ const home = process.env.HOME || homedir8();
167452
+ const opencodePaths = [
167453
+ join11(home, ".config", "opencode", "opencode.jsonc"),
167454
+ join11(home, ".config", "opencode", "opencode.json")
167455
+ ];
167456
+ const piPaths = [join11(home, ".pi", "agent", "settings.json")];
167457
+ const checkedPaths = [...opencodePaths, ...piPaths];
167458
+ let opencode = false;
167459
+ for (const path4 of opencodePaths) {
167460
+ try {
167461
+ const config2 = parseConfig(path4);
167462
+ if (hasAftAtKeys(config2, ["plugin", "plugins", "mcp", "mcp_servers"])) {
167463
+ opencode = true;
167464
+ break;
167465
+ }
167466
+ } catch {}
167467
+ }
167468
+ let pi = false;
167469
+ for (const path4 of piPaths) {
167470
+ try {
167471
+ const config2 = parseConfig(path4);
167472
+ if (hasAftAtKeys(config2, ["packages", "extensions"])) {
167473
+ pi = true;
167474
+ break;
167475
+ }
167476
+ const agent = config2?.agent;
167477
+ if (hasAftAtKeys(agent, ["packages", "extensions"])) {
167478
+ pi = true;
167479
+ break;
167480
+ }
167481
+ } catch {}
167482
+ }
167483
+ const detected = opencode || pi;
167484
+ return {
167485
+ available: overrideAvailability ?? detected,
167486
+ opencode,
167487
+ pi,
167488
+ checkedPaths
167489
+ };
167490
+ }
167491
+ function isAftAvailable() {
167492
+ return getAftAvailability().available;
167224
167493
  }
167225
167494
 
167226
- // src/features/magic-context/key-files/storage-key-files.ts
167227
- init_logger();
167228
- function getKeyFiles(db, sessionId) {
167229
- try {
167230
- const row = db.prepare("SELECT key_files FROM session_meta WHERE session_id = ?").get(sessionId);
167231
- if (!row?.key_files)
167232
- return [];
167233
- const parsed = JSON.parse(row.key_files);
167234
- if (!Array.isArray(parsed))
167235
- return [];
167236
- return parsed.filter((e) => typeof e === "object" && e !== null && typeof e.filePath === "string" && typeof e.tokens === "number");
167237
- } catch {
167495
+ // src/features/magic-context/key-files/identify-key-files.ts
167496
+ init_project_key_files();
167497
+
167498
+ // src/features/magic-context/key-files/read-history.ts
167499
+ import { realpathSync as realpathSync2 } from "node:fs";
167500
+ import { relative, resolve as resolve5 } from "node:path";
167501
+ function toMs(value) {
167502
+ if (typeof value === "number" && Number.isFinite(value))
167503
+ return value;
167504
+ if (typeof value === "string") {
167505
+ const n = Number(value);
167506
+ if (Number.isFinite(n))
167507
+ return n;
167508
+ const parsed = Date.parse(value);
167509
+ if (Number.isFinite(parsed))
167510
+ return parsed;
167511
+ }
167512
+ return 0;
167513
+ }
167514
+ function toPositiveInt(value) {
167515
+ const n = Number(value);
167516
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
167517
+ }
167518
+ function coalesceRanges(ranges) {
167519
+ if (ranges.length === 0)
167238
167520
  return [];
167521
+ const sorted = [...ranges].sort((a, b) => a.start - b.start || a.end - b.end);
167522
+ const merged = [];
167523
+ for (const range of sorted) {
167524
+ const last = merged.at(-1);
167525
+ if (last && range.start <= last.end + 10) {
167526
+ last.end = Math.max(last.end, range.end);
167527
+ last.count += range.count;
167528
+ last.lastReadAt = Math.max(last.lastReadAt, range.lastReadAt);
167529
+ } else {
167530
+ merged.push({ ...range });
167531
+ }
167239
167532
  }
167533
+ return merged.sort((a, b) => b.lastReadAt - a.lastReadAt || b.count - a.count).slice(0, 3);
167240
167534
  }
167241
- function setKeyFiles(db, sessionId, files) {
167535
+ function normalizeProjectRelativePath(projectPath, filePath) {
167536
+ const root = realpathSync2(projectPath);
167537
+ const abs = filePath.startsWith("/") ? resolve5(filePath) : resolve5(root, filePath);
167538
+ let real = abs;
167242
167539
  try {
167243
- db.prepare("INSERT OR IGNORE INTO session_meta (session_id) VALUES (?)").run(sessionId);
167244
- db.prepare("UPDATE session_meta SET key_files = ? WHERE session_id = ?").run(JSON.stringify(files), sessionId);
167245
- } catch (error51) {
167246
- sessionLog(sessionId, "failed to persist key files:", error51);
167540
+ real = realpathSync2(abs);
167541
+ } catch {}
167542
+ if (!real.startsWith(`${root}/`) && real !== root)
167543
+ return null;
167544
+ const rel = relative(root, real).replaceAll("\\", "/");
167545
+ if (!rel || rel.startsWith(".."))
167546
+ return null;
167547
+ return rel;
167548
+ }
167549
+ function primarySessionIds(db) {
167550
+ try {
167551
+ const rows = db.prepare("SELECT session_id AS sessionId FROM session_meta WHERE is_subagent = 0").all();
167552
+ return new Set(rows.map((row) => row.sessionId));
167553
+ } catch {
167554
+ return new Set;
167247
167555
  }
167248
167556
  }
167249
- function greedyFitFiles(rankedFiles, tokenBudget) {
167250
- const selected = [];
167251
- let remainingBudget = tokenBudget;
167252
- for (const file2 of rankedFiles) {
167253
- if (file2.tokens <= 0)
167557
+ function collectKeyFileCandidates(args) {
167558
+ const primaryIds = primarySessionIds(args.magicDb);
167559
+ if (primaryIds.size === 0)
167560
+ return [];
167561
+ const reads = args.openCodeDb.prepare(`SELECT p.session_id,
167562
+ json_extract(json_extract(p.data, '$.state'), '$.input.filePath') AS file_path,
167563
+ json_extract(json_extract(p.data, '$.state'), '$.input.startLine') AS start_line,
167564
+ json_extract(json_extract(p.data, '$.state'), '$.input.endLine') AS end_line,
167565
+ json_extract(json_extract(p.data, '$.state'), '$.input.offset') AS offset_value,
167566
+ json_extract(json_extract(p.data, '$.state'), '$.input.limit') AS limit_value,
167567
+ LENGTH(json_extract(json_extract(p.data, '$.state'), '$.output')) AS output_bytes,
167568
+ p.time_created
167569
+ FROM part p
167570
+ WHERE json_extract(p.data, '$.type') = 'tool'
167571
+ AND json_extract(p.data, '$.tool') = 'read'
167572
+ AND json_extract(json_extract(p.data, '$.state'), '$.input.filePath') IS NOT NULL`).all();
167573
+ const byPath = new Map;
167574
+ for (const row of reads) {
167575
+ if (!primaryIds.has(row.session_id) || !row.file_path)
167254
167576
  continue;
167255
- if (file2.tokens > remainingBudget)
167577
+ const rel = normalizeProjectRelativePath(args.projectPath, row.file_path);
167578
+ if (!rel)
167256
167579
  continue;
167257
- selected.push({ filePath: file2.filePath, tokens: file2.tokens });
167258
- remainingBudget -= file2.tokens;
167259
- if (remainingBudget <= 0)
167260
- break;
167580
+ const timestamp = toMs(row.time_created);
167581
+ const candidate = byPath.get(rel) ?? {
167582
+ path: rel,
167583
+ totalReads: 0,
167584
+ rangedReads: 0,
167585
+ fullReads: 0,
167586
+ editCount: 0,
167587
+ latestReadBytes: 0,
167588
+ firstReadAt: timestamp || Date.now(),
167589
+ lastReadAt: 0,
167590
+ ranges: [],
167591
+ rangeMap: new Map
167592
+ };
167593
+ candidate.totalReads++;
167594
+ candidate.firstReadAt = Math.min(candidate.firstReadAt, timestamp || candidate.firstReadAt);
167595
+ candidate.lastReadAt = Math.max(candidate.lastReadAt, timestamp);
167596
+ if (timestamp >= candidate.lastReadAt)
167597
+ candidate.latestReadBytes = Number(row.output_bytes ?? 0);
167598
+ const start = toPositiveInt(row.start_line) ?? toPositiveInt(row.offset_value);
167599
+ const explicitEnd = toPositiveInt(row.end_line);
167600
+ const limit = toPositiveInt(row.limit_value);
167601
+ const end = explicitEnd ?? (start && limit ? start + limit - 1 : null);
167602
+ if (start && end) {
167603
+ candidate.rangedReads++;
167604
+ const key = `${start}:${end}`;
167605
+ const existing = candidate.rangeMap.get(key);
167606
+ if (existing) {
167607
+ existing.count++;
167608
+ existing.lastReadAt = Math.max(existing.lastReadAt, timestamp);
167609
+ } else {
167610
+ candidate.rangeMap.set(key, { start, end, count: 1, lastReadAt: timestamp });
167611
+ }
167612
+ } else {
167613
+ candidate.fullReads++;
167614
+ }
167615
+ byPath.set(rel, candidate);
167616
+ }
167617
+ const edits = args.openCodeDb.prepare(`SELECT p.session_id,
167618
+ json_extract(json_extract(p.data, '$.state'), '$.input.filePath') AS file_path,
167619
+ COUNT(*) AS edit_count
167620
+ FROM part p
167621
+ WHERE json_extract(p.data, '$.type') = 'tool'
167622
+ AND json_extract(p.data, '$.tool') IN ('edit', 'write', 'mcp_edit', 'mcp_write')
167623
+ AND json_extract(json_extract(p.data, '$.state'), '$.input.filePath') IS NOT NULL
167624
+ GROUP BY p.session_id, file_path`).all();
167625
+ for (const row of edits) {
167626
+ if (!primaryIds.has(row.session_id) || !row.file_path)
167627
+ continue;
167628
+ const rel = normalizeProjectRelativePath(args.projectPath, row.file_path);
167629
+ if (!rel)
167630
+ continue;
167631
+ const candidate = byPath.get(rel);
167632
+ if (candidate)
167633
+ candidate.editCount += Number(row.edit_count ?? 0);
167261
167634
  }
167262
- return selected;
167635
+ return [...byPath.values()].filter((candidate) => candidate.totalReads >= args.minReads).map(({ rangeMap, ...candidate }) => ({
167636
+ ...candidate,
167637
+ ranges: coalesceRanges([...rangeMap.values()])
167638
+ })).sort((a, b) => b.totalReads - a.totalReads || b.lastReadAt - a.lastReadAt).slice(0, 200);
167263
167639
  }
167264
167640
 
167641
+ // src/features/magic-context/key-files/storage-key-files.ts
167642
+ init_logger();
167643
+
167265
167644
  // src/features/magic-context/key-files/identify-key-files.ts
167266
167645
  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.";
167267
- function buildKeyFilesPrompt(candidates, tokenBudget, minReads) {
167268
- const statsText = candidates.map((s) => `- **${s.filePath}** — ${s.fullReadCount} full reads, ${s.editCount} edits, ~${s.latestReadTokens} tokens`).join(`
167646
+ class KeyFilesValidationError extends Error {
167647
+ constructor(message) {
167648
+ super(message);
167649
+ this.name = "KeyFilesValidationError";
167650
+ }
167651
+ }
167652
+ function computeGenerationConfigHash(config2) {
167653
+ return sha256(JSON.stringify({ budget: config2.token_budget, min_reads: config2.min_reads }));
167654
+ }
167655
+ function extractJsonObject(text) {
167656
+ const fenced = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
167657
+ if (fenced?.[1])
167658
+ return fenced[1];
167659
+ const object2 = text.match(/\{[\s\S]*\}/);
167660
+ if (object2?.[0])
167661
+ return object2[0];
167662
+ throw new KeyFilesValidationError("missing JSON object");
167663
+ }
167664
+ function formatCandidates(candidates) {
167665
+ return candidates.map((candidate) => {
167666
+ const ranges = candidate.ranges.map((range) => `${range.start}-${range.end} (${range.count}x)`).join(", ");
167667
+ return `| ${candidate.path} | ${candidate.totalReads} | ${new Date(candidate.lastReadAt).toISOString()} | ${ranges || "full reads"} |`;
167668
+ }).join(`
167269
167669
  `);
167270
- return `## Identify Key Files for Pinning
167670
+ }
167671
+ function buildV6KeyFilesPrompt(args) {
167672
+ const current = args.currentRows.map((row) => `- ${row.path}: ${row.localTokenEstimate} tokens, stale=${row.staleReason ?? "fresh"}`).join(`
167673
+ `);
167674
+ return `## Task: Identify project key files
167271
167675
 
167272
- The following files were fully read ${minReads}+ times during a coding session.
167273
- Identify which ones are **core orientation files** worth keeping permanently in context.
167676
+ You are deciding which files this project's primary agent should always see as orientation context. The injection budget is **${args.config.token_budget} tokens** for all files combined.
167274
167677
 
167275
- ### Signals of a core orientation file:
167276
- - Read many times across different phases of work (not clustered in one task)
167277
- - Read without editing — consulted for understanding, not modification
167278
- - Contains architecture, configuration, types, or key abstractions
167678
+ ## Read history (primary sessions only, project-scoped)
167279
167679
 
167280
- ### Signals of a NON-core file (exclude):
167281
- - Read many times but always edited actively working on it
167282
- - Very large (>5000 tokens) — too expensive to pin
167283
- - Test files, scripts, or generated files
167680
+ | path | total reads | last read | common line ranges |
167681
+ | --- | ---: | --- | --- |
167682
+ ${formatCandidates(args.candidates)}
167284
167683
 
167285
- ### Token budget: ${tokenBudget} tokens total
167684
+ ## Current key files (if any)
167286
167685
 
167287
- ### Files:
167288
- ${statsText}
167686
+ ${current || "(none)"}
167687
+
167688
+ ## Tools available
167689
+
167690
+ - \`aft_outline(target=<path>)\` — get symbol outline for a file
167691
+ - \`aft_zoom(filePath=<path>, symbol=<name>)\` — get full source of one symbol
167692
+
167693
+ ## Output (strict JSON)
167289
167694
 
167290
- ### Output Format
167291
- Return a JSON array ranked by importance (most important first):
167292
167695
  \`\`\`json
167293
- [
167294
- {"filePath": "src/path/to/file.ts", "tokens": 2500, "reason": "brief reason"}
167295
- ]
167696
+ {
167697
+ "no_change": false,
167698
+ "files": [
167699
+ {
167700
+ "path": "src/example.ts",
167701
+ "content": "...outline + symbol bodies stitched from src/example.ts only...",
167702
+ "approx_token_estimate": 1200
167703
+ }
167704
+ ]
167705
+ }
167296
167706
  \`\`\`
167297
167707
 
167298
- Only include files you're confident are true orientation files. Return empty array if none qualify.`;
167708
+ If current key files are still optimal, return \`{ "no_change": true, "files": [] }\`.
167709
+
167710
+ Rules:
167711
+ - Each row's content MUST come from ONLY the one file at path. If two files matter, emit two rows.
167712
+ - Do NOT emit source_files. One row = one file = one hash.
167713
+ - Total approx_token_estimate must be < ${args.config.token_budget}.
167714
+ - path must be relative, no .., no absolute paths, no symlinks escaping project root.
167715
+ - path must be unique case-sensitively and case-insensitively.
167716
+ - content must be plain text; no XML tags inside.`;
167717
+ }
167718
+ function validateLlmOutput(raw, config2, projectPath) {
167719
+ let obj;
167720
+ try {
167721
+ obj = JSON.parse(extractJsonObject(raw));
167722
+ } catch (error51) {
167723
+ if (error51 instanceof KeyFilesValidationError)
167724
+ throw error51;
167725
+ throw new KeyFilesValidationError(`invalid JSON: ${getErrorMessage(error51)}`);
167726
+ }
167727
+ if (!obj || typeof obj !== "object")
167728
+ throw new KeyFilesValidationError("output must be object");
167729
+ const record2 = obj;
167730
+ if (typeof record2.no_change !== "boolean")
167731
+ throw new KeyFilesValidationError("missing no_change");
167732
+ if (!Array.isArray(record2.files))
167733
+ throw new KeyFilesValidationError("missing files array");
167734
+ if (record2.no_change && record2.files.length > 0)
167735
+ throw new KeyFilesValidationError("no_change=true with files");
167736
+ if (!record2.no_change && record2.files.length === 0)
167737
+ throw new KeyFilesValidationError("no_change=false with empty files");
167738
+ const seen = new Set;
167739
+ const seenLower = new Set;
167740
+ const files = [];
167741
+ for (const item of record2.files) {
167742
+ if (!item || typeof item !== "object")
167743
+ throw new KeyFilesValidationError("bad file entry");
167744
+ const file2 = item;
167745
+ if ("source_files" in file2) {
167746
+ throw new KeyFilesValidationError(`source_files field not allowed (one file per row): ${String(file2.path)}`);
167747
+ }
167748
+ if (typeof file2.path !== "string" || file2.path.length === 0)
167749
+ throw new KeyFilesValidationError("bad path");
167750
+ if (file2.path.startsWith("/") || file2.path.includes(".."))
167751
+ throw new KeyFilesValidationError(`escape: ${file2.path}`);
167752
+ if (seen.has(file2.path))
167753
+ throw new KeyFilesValidationError(`dup path: ${file2.path}`);
167754
+ if (seenLower.has(file2.path.toLowerCase()))
167755
+ throw new KeyFilesValidationError(`case-dup: ${file2.path}`);
167756
+ seen.add(file2.path);
167757
+ seenLower.add(file2.path.toLowerCase());
167758
+ if (!isRelativeProjectFile(projectPath, file2.path))
167759
+ throw new KeyFilesValidationError(`unreadable: ${file2.path}`);
167760
+ if (typeof file2.content !== "string")
167761
+ throw new KeyFilesValidationError(`bad content: ${file2.path}`);
167762
+ if (file2.content.length > 1e5)
167763
+ throw new KeyFilesValidationError(`content >100KB: ${file2.path}`);
167764
+ if (typeof file2.approx_token_estimate !== "number" || file2.approx_token_estimate < 0) {
167765
+ throw new KeyFilesValidationError(`bad token estimate: ${file2.path}`);
167766
+ }
167767
+ const local = estimateTokens(file2.content);
167768
+ if (file2.approx_token_estimate > 0 && (local / file2.approx_token_estimate > 1.5 || local / file2.approx_token_estimate < 0.5)) {
167769
+ log(`key-files: token estimate divergence for ${file2.path}: claimed=${file2.approx_token_estimate}, plugin=${local}`);
167770
+ }
167771
+ files.push({
167772
+ path: file2.path,
167773
+ content: file2.content,
167774
+ approx_token_estimate: file2.approx_token_estimate,
167775
+ local_token_estimate: local
167776
+ });
167777
+ }
167778
+ const total = files.reduce((sum, file2) => sum + file2.local_token_estimate, 0);
167779
+ if (total > config2.token_budget)
167780
+ throw new KeyFilesValidationError(`total ${total} > budget ${config2.token_budget}`);
167781
+ return { no_change: record2.no_change, files };
167782
+ }
167783
+ function commitKeyFiles(args) {
167784
+ if (args.validated.no_change)
167785
+ return null;
167786
+ const projectPath = resolveProjectPath(args.projectPath) ?? args.projectPath;
167787
+ const resolved = resolveCommitFiles(projectPath, args.validated.files.map((file2) => ({
167788
+ path: file2.path,
167789
+ content: file2.content,
167790
+ localTokenEstimate: file2.local_token_estimate
167791
+ })));
167792
+ const generatedAt = Date.now();
167793
+ const bump = args.bumpVersion ?? bumpKeyFilesVersion;
167794
+ args.db.exec("BEGIN IMMEDIATE");
167795
+ let committed = false;
167796
+ try {
167797
+ if (!peekLeaseHolderAndExpiry(args.db, args.leaseHolderId)) {
167798
+ log(`key-files commit aborted: lease lost (holder ${args.leaseHolderId})`);
167799
+ return null;
167800
+ }
167801
+ args.db.prepare("DELETE FROM project_key_files WHERE project_path = ?").run(projectPath);
167802
+ insertResolvedKeyFiles(args.db, projectPath, resolved, generatedAt, args.modelId, args.configHash);
167803
+ const version2 = bump(args.db, projectPath);
167804
+ args.db.exec("COMMIT");
167805
+ committed = true;
167806
+ log(`key-files committed: ${resolved.length} files, version=${version2}, ${resolved.filter((r) => r.staleReason).length} pre-stale`);
167807
+ return version2;
167808
+ } finally {
167809
+ if (!committed) {
167810
+ try {
167811
+ args.db.exec("ROLLBACK");
167812
+ } catch {}
167813
+ }
167814
+ }
167815
+ }
167816
+ async function runKeyFilesLlm(args) {
167817
+ const createResponse = await args.client.session.create({
167818
+ body: {
167819
+ ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
167820
+ title: "magic-context-dream-key-files-v6"
167821
+ },
167822
+ query: { directory: args.projectPath }
167823
+ });
167824
+ const created = normalizeSDKResponse(createResponse, null, {
167825
+ preferResponseOnMissingData: true
167826
+ });
167827
+ const agentSessionId = typeof created?.id === "string" ? created.id : null;
167828
+ if (!agentSessionId)
167829
+ throw new Error("Could not create key-file identification session.");
167830
+ try {
167831
+ await promptSyncWithModelSuggestionRetry(args.client, {
167832
+ path: { id: agentSessionId },
167833
+ query: { directory: args.projectPath },
167834
+ body: {
167835
+ agent: DREAMER_AGENT,
167836
+ system: KEY_FILES_SYSTEM_PROMPT,
167837
+ parts: [{ type: "text", text: args.prompt, synthetic: true }]
167838
+ }
167839
+ }, {
167840
+ timeoutMs: Math.min(Math.max(0, args.deadline - Date.now()), 5 * 60 * 1000),
167841
+ fallbackModels: args.fallbackModels,
167842
+ callContext: "dreamer:key-files-v6"
167843
+ });
167844
+ const messagesResponse = await args.client.session.messages({
167845
+ path: { id: agentSessionId },
167846
+ query: { directory: args.projectPath }
167847
+ });
167848
+ const messages = normalizeSDKResponse(messagesResponse, [], {
167849
+ preferResponseOnMissingData: true
167850
+ });
167851
+ const text = extractLatestAssistantText(messages);
167852
+ if (!text)
167853
+ throw new Error("Dreamer returned no key-files output.");
167854
+ return text;
167855
+ } finally {
167856
+ await args.client.session.delete({ path: { id: agentSessionId } }).catch(() => {
167857
+ return;
167858
+ });
167859
+ }
167299
167860
  }
167300
- function parseKeyFilesOutput(text) {
167301
- const jsonMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/) ?? text.match(/\[[\s\S]*\]/);
167302
- if (!jsonMatch)
167303
- return [];
167861
+ async function runKeyFilesTask(args) {
167862
+ if (!args.config.enabled)
167863
+ return { committedVersion: null, candidates: 0, noChange: false };
167864
+ if (!isAftAvailable()) {
167865
+ log("[key-files] AFT not available, skipping key-files task");
167866
+ return { committedVersion: null, candidates: 0, noChange: false };
167867
+ }
167868
+ const projectPath = resolveProjectPath(args.projectPath) ?? args.projectPath;
167869
+ const candidates = collectKeyFileCandidates({
167870
+ openCodeDb: args.openCodeDb,
167871
+ magicDb: args.db,
167872
+ projectPath,
167873
+ minReads: args.config.min_reads
167874
+ });
167875
+ if (candidates.length === 0)
167876
+ return { committedVersion: null, candidates: 0, noChange: false };
167877
+ const currentRows = readCurrentKeyFiles(args.db, projectPath);
167878
+ const configHash = computeGenerationConfigHash(args.config);
167879
+ const allRowsFreshAndCurrent = currentRows.length > 0 && currentRows.every((row) => {
167880
+ if (row.staleReason !== null || row.generationConfigHash !== configHash)
167881
+ return false;
167882
+ try {
167883
+ return sha256(readFileSync10(join13(projectPath, row.path))) === row.contentHash;
167884
+ } catch {
167885
+ return false;
167886
+ }
167887
+ });
167888
+ const currentPaths = new Set(currentRows.map((row) => row.path));
167889
+ if (allRowsFreshAndCurrent && candidates.every((candidate) => currentPaths.has(candidate.path))) {
167890
+ log(`key-files: no_change short-circuit (${currentRows.length} rows fresh)`);
167891
+ return { committedVersion: null, candidates: candidates.length, noChange: true };
167892
+ }
167893
+ const prompt = buildV6KeyFilesPrompt({ candidates, currentRows, config: args.config });
167894
+ let validated;
167895
+ const leaseInterval = setInterval(() => {
167896
+ try {
167897
+ if (!renewLease(args.db, args.holderId)) {
167898
+ log("[key-files] lease renewal failed during LLM call");
167899
+ }
167900
+ } catch (error51) {
167901
+ log(`[key-files] lease renewal threw: ${getErrorMessage(error51)}`);
167902
+ }
167903
+ }, 60000);
167304
167904
  try {
167305
- const raw = jsonMatch[1] ?? jsonMatch[0];
167306
- const parsed = JSON.parse(raw);
167307
- if (!Array.isArray(parsed))
167308
- return [];
167309
- 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 }));
167310
- } catch {
167311
- return [];
167905
+ try {
167906
+ const raw = await runKeyFilesLlm({
167907
+ client: args.client,
167908
+ parentSessionId: args.parentSessionId,
167909
+ projectPath,
167910
+ prompt,
167911
+ deadline: args.deadline,
167912
+ fallbackModels: args.fallbackModels
167913
+ });
167914
+ validated = validateLlmOutput(raw, args.config, projectPath);
167915
+ } catch (error51) {
167916
+ log(`[key-files] LLM validation failed: ${getErrorMessage(error51)}`);
167917
+ throw error51;
167918
+ }
167919
+ if (validated.no_change)
167920
+ return { committedVersion: null, candidates: candidates.length, noChange: true };
167921
+ const committedVersion = commitKeyFiles({
167922
+ db: args.db,
167923
+ projectPath,
167924
+ validated,
167925
+ configHash,
167926
+ modelId: args.fallbackModels?.[0] ?? "dreamer",
167927
+ leaseHolderId: args.holderId
167928
+ });
167929
+ renewLease(args.db, args.holderId);
167930
+ return { committedVersion, candidates: candidates.length, noChange: false };
167931
+ } finally {
167932
+ clearInterval(leaseInterval);
167312
167933
  }
167313
167934
  }
167314
- function getKeyFileCandidates(openCodeDb, sessionId, minReads, tokenBudget, projectDirectory) {
167315
- const stats = getSessionReadStats(openCodeDb, sessionId, minReads);
167316
- const maxPerFileTokens = Math.min(tokenBudget / 2, 5000);
167317
- const projectPrefix = projectDirectory ? `${projectDirectory.replace(/\/$/, "")}/` : undefined;
167318
- return stats.filter((s) => s.latestReadTokens > 0 && s.latestReadTokens <= maxPerFileTokens && (!projectPrefix || s.filePath.startsWith(projectPrefix)));
167319
- }
167320
- function applyKeyFileResults(db, sessionId, llmRanked, tokenBudget, candidatePaths) {
167321
- const filtered = candidatePaths ? llmRanked.filter((f) => candidatePaths.has(f.filePath)) : llmRanked;
167322
- const selected = greedyFitFiles(filtered, tokenBudget);
167323
- setKeyFiles(db, sessionId, selected);
167324
- const totalTokens = selected.reduce((sum, f) => sum + f.tokens, 0);
167325
- log(`[key-files][${sessionId}] pinned ${selected.length} files (${totalTokens} tokens): ${selected.map((f) => f.filePath).join(", ")}`);
167326
- return { filesIdentified: selected.length, totalTokens };
167327
- }
167328
- function heuristicKeyFileSelection(db, sessionId, candidates, tokenBudget) {
167329
- const scored = candidates.map((c) => ({
167330
- filePath: c.filePath,
167331
- tokens: c.latestReadTokens,
167332
- score: c.fullReadCount * 2 - c.editCount * 3
167333
- })).filter((c) => c.score > 0).sort((a, b) => b.score - a.score);
167334
- const selected = greedyFitFiles(scored, tokenBudget);
167335
- setKeyFiles(db, sessionId, selected);
167336
- const totalTokens = selected.reduce((sum, f) => sum + f.tokens, 0);
167337
- log(`[key-files][${sessionId}] heuristic pinned ${selected.length} files (${totalTokens} tokens)`);
167338
- return { filesIdentified: selected.length, totalTokens };
167339
- }
167340
167935
 
167341
167936
  // src/features/magic-context/dreamer/runner.ts
167342
167937
  init_storage_memory();
@@ -167568,12 +168163,12 @@ function shouldSkipCircuitBreaker(error51, brief) {
167568
168163
  function logWithStackHead(message, stackHead) {
167569
168164
  log(message, stackHead ? { stackHead } : undefined);
167570
168165
  }
167571
- function getOpenCodeDbPath2() {
167572
- return join12(getDataDir(), "opencode", "opencode.db");
168166
+ function getOpenCodeDbPath() {
168167
+ return join14(getDataDir(), "opencode", "opencode.db");
167573
168168
  }
167574
168169
  function openOpenCodeDb() {
167575
- const dbPath = getOpenCodeDbPath2();
167576
- if (!existsSync9(dbPath)) {
168170
+ const dbPath = getOpenCodeDbPath();
168171
+ if (!existsSync11(dbPath)) {
167577
168172
  log(`[key-files] OpenCode DB not found at ${dbPath} — skipping`);
167578
168173
  return null;
167579
168174
  }
@@ -167586,190 +168181,6 @@ function openOpenCodeDb() {
167586
168181
  return null;
167587
168182
  }
167588
168183
  }
167589
- function isSessionIdRow(row) {
167590
- if (row === null || typeof row !== "object") {
167591
- return false;
167592
- }
167593
- return typeof row.sessionId === "string";
167594
- }
167595
- function hasExplicitEmptyKeyFilesOutput(text) {
167596
- return /```(?:json)?\s*\[\s*\]\s*```/s.test(text) || /^\s*\[\s*\]\s*$/s.test(text);
167597
- }
167598
- async function getActiveProjectSessionIds(args) {
167599
- try {
167600
- const { withReadOnlySessionDb: withReadOnlySessionDb2 } = await init_read_session_db().then(() => exports_read_session_db);
167601
- const projectSessionIds = withReadOnlySessionDb2((openCodeDb) => {
167602
- const bareIdentity = args.projectIdentity.replace(/^git:/, "");
167603
- const rows = openCodeDb.prepare("SELECT id FROM session WHERE project_id = ? AND parent_id IS NULL ORDER BY time_updated DESC").all(bareIdentity);
167604
- return new Set(rows.map((r) => r.id));
167605
- });
167606
- if (projectSessionIds.size === 0) {
167607
- return [];
167608
- }
167609
- 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));
167610
- } catch (error51) {
167611
- sessionLog(args.projectIdentity, `key-files: OpenCode DB lookup failed, falling back to SDK list: ${getErrorMessage(error51)}`);
167612
- const listResponse = await args.client.session.list({
167613
- query: { directory: args.sessionDirectory ?? args.projectIdentity }
167614
- });
167615
- const sessions = normalizeSDKResponse(listResponse, [], {
167616
- preferResponseOnMissingData: true
167617
- });
167618
- const projectSessionIds = new Set(sessions.map((session) => typeof session?.id === "string" ? session.id : null).filter((sessionId) => Boolean(sessionId)));
167619
- if (projectSessionIds.size === 0) {
167620
- return [];
167621
- }
167622
- 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));
167623
- }
167624
- }
167625
- async function identifyKeyFilesForSession(args) {
167626
- let openCodeDb = null;
167627
- try {
167628
- openCodeDb = openOpenCodeDb();
167629
- if (!openCodeDb) {
167630
- return;
167631
- }
167632
- const candidates = getKeyFileCandidates(openCodeDb, args.sessionId, args.config.min_reads, args.config.token_budget, args.sessionDirectory);
167633
- if (candidates.length === 0) {
167634
- log(`[key-files][${args.sessionId}] no candidates found — skipping`);
167635
- return;
167636
- }
167637
- const prompt = buildKeyFilesPrompt(candidates, args.config.token_budget, args.config.min_reads);
167638
- const applyHeuristicFallback = () => {
167639
- heuristicKeyFileSelection(args.db, args.sessionId, candidates, args.config.token_budget);
167640
- };
167641
- let agentSessionId = null;
167642
- const abortController = new AbortController;
167643
- const leaseInterval = setInterval(() => {
167644
- try {
167645
- if (!renewLease(args.db, args.holderId)) {
167646
- log(`[key-files][${args.sessionId}] lease renewal failed — aborting`);
167647
- args.onLeaseLost?.(`key-files:${args.sessionId}`);
167648
- abortController.abort();
167649
- }
167650
- } catch (error51) {
167651
- args.onLeaseLost?.(`key-files:${args.sessionId}`, error51);
167652
- abortController.abort();
167653
- }
167654
- }, 60000);
167655
- try {
167656
- const createResponse = await args.client.session.create({
167657
- body: {
167658
- ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
167659
- title: `magic-context-dream-key-files-${args.sessionId.slice(0, 12)}`
167660
- },
167661
- query: { directory: args.sessionDirectory }
167662
- });
167663
- const created = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
167664
- agentSessionId = typeof created?.id === "string" ? created.id : null;
167665
- if (!agentSessionId) {
167666
- throw new Error("Could not create key-file identification session.");
167667
- }
167668
- log(`[key-files][${args.sessionId}] child session created ${agentSessionId}`);
167669
- const remainingMs = Math.max(0, args.deadline - Date.now());
167670
- await promptSyncWithModelSuggestionRetry(args.client, {
167671
- path: { id: agentSessionId },
167672
- query: { directory: args.sessionDirectory },
167673
- body: {
167674
- agent: DREAMER_AGENT,
167675
- system: KEY_FILES_SYSTEM_PROMPT,
167676
- parts: [{ type: "text", text: prompt, synthetic: true }]
167677
- }
167678
- }, {
167679
- timeoutMs: Math.min(remainingMs, 300000),
167680
- signal: abortController.signal,
167681
- fallbackModels: args.fallbackModels,
167682
- callContext: `dreamer:key-files:${args.sessionId.slice(0, 12)}`
167683
- });
167684
- const messagesResponse = await args.client.session.messages({
167685
- path: { id: agentSessionId },
167686
- query: { directory: args.sessionDirectory }
167687
- });
167688
- const messages = normalizeSDKResponse(messagesResponse, [], {
167689
- preferResponseOnMissingData: true
167690
- });
167691
- const responseText = extractLatestAssistantText(messages);
167692
- if (!responseText) {
167693
- log(`[key-files][${args.sessionId}] no response from agent — using heuristic fallback`);
167694
- applyHeuristicFallback();
167695
- return;
167696
- }
167697
- const parsed = parseKeyFilesOutput(responseText);
167698
- if (parsed.length > 0 || hasExplicitEmptyKeyFilesOutput(responseText)) {
167699
- const candidatePaths = new Set(candidates.map((c) => c.filePath));
167700
- applyKeyFileResults(args.db, args.sessionId, parsed, args.config.token_budget, candidatePaths);
167701
- return;
167702
- }
167703
- log(`[key-files][${args.sessionId}] could not parse agent output — using heuristic fallback`);
167704
- applyHeuristicFallback();
167705
- } catch (error51) {
167706
- if (args.isLeaseLost?.() || abortController.signal.aborted) {
167707
- log(`[key-files][${args.sessionId}] lease lost during identification — skipping heuristic fallback`);
167708
- throw error51;
167709
- }
167710
- log(`[key-files][${args.sessionId}] identification failed: ${getErrorMessage(error51)} — using heuristic fallback`);
167711
- try {
167712
- applyHeuristicFallback();
167713
- } catch (fallbackError) {
167714
- log(`[key-files][${args.sessionId}] heuristic fallback failed: ${getErrorMessage(fallbackError)}`);
167715
- }
167716
- } finally {
167717
- clearInterval(leaseInterval);
167718
- if (agentSessionId) {
167719
- await args.client.session.delete({
167720
- path: { id: agentSessionId },
167721
- query: { directory: args.sessionDirectory }
167722
- }).catch((error51) => {
167723
- log(`[key-files][${args.sessionId}] session cleanup failed: ${getErrorMessage(error51)}`);
167724
- });
167725
- }
167726
- }
167727
- } finally {
167728
- if (openCodeDb) {
167729
- try {
167730
- closeQuietly(openCodeDb);
167731
- } catch (error51) {
167732
- log(`[key-files][${args.sessionId}] failed to close OpenCode DB: ${getErrorMessage(error51)}`);
167733
- }
167734
- }
167735
- }
167736
- }
167737
- async function identifyKeyFiles(args) {
167738
- const sessionIds = await getActiveProjectSessionIds({
167739
- db: args.db,
167740
- client: args.client,
167741
- projectIdentity: args.projectIdentity,
167742
- sessionDirectory: args.sessionDirectory
167743
- });
167744
- if (sessionIds.length === 0) {
167745
- log(`[key-files] no active sessions found for ${args.projectIdentity}`);
167746
- return;
167747
- }
167748
- log(`[key-files] evaluating ${sessionIds.length} active session(s) for ${args.projectIdentity}`);
167749
- for (const sessionId of sessionIds) {
167750
- if (args.isLeaseLost?.()) {
167751
- log("[key-files] lease lost — stopping key-file identification");
167752
- break;
167753
- }
167754
- if (Date.now() > args.deadline) {
167755
- log("[key-files] deadline reached — stopping key-file identification");
167756
- break;
167757
- }
167758
- await identifyKeyFilesForSession({
167759
- db: args.db,
167760
- client: args.client,
167761
- parentSessionId: args.parentSessionId,
167762
- sessionDirectory: args.sessionDirectory,
167763
- holderId: args.holderId,
167764
- fallbackModels: args.fallbackModels,
167765
- onLeaseLost: args.onLeaseLost,
167766
- isLeaseLost: args.isLeaseLost,
167767
- deadline: args.deadline,
167768
- sessionId,
167769
- config: args.config
167770
- });
167771
- }
167772
- }
167773
168184
  async function runDream(args) {
167774
168185
  const holderId = crypto.randomUUID();
167775
168186
  const startedAt = Date.now();
@@ -167888,8 +168299,8 @@ async function runDream(args) {
167888
168299
  try {
167889
168300
  const docsDir = args.sessionDirectory ?? args.projectIdentity;
167890
168301
  const existingDocs = taskName === "maintain-docs" ? {
167891
- architecture: existsSync9(join12(docsDir, "ARCHITECTURE.md")),
167892
- structure: existsSync9(join12(docsDir, "STRUCTURE.md"))
168302
+ architecture: existsSync11(join14(docsDir, "ARCHITECTURE.md")),
168303
+ structure: existsSync11(join14(docsDir, "STRUCTURE.md"))
167893
168304
  } : undefined;
167894
168305
  const userMemories = taskName === "archive-stale" ? getActiveUserMemories(args.db).map((um) => ({
167895
168306
  id: um.id,
@@ -167990,8 +168401,7 @@ async function runDream(args) {
167990
168401
  clearInterval(leaseRenewalInterval);
167991
168402
  if (agentSessionId) {
167992
168403
  await args.client.session.delete({
167993
- path: { id: agentSessionId },
167994
- query: { directory: args.sessionDirectory ?? args.projectIdentity }
168404
+ path: { id: agentSessionId }
167995
168405
  }).catch((error51) => {
167996
168406
  log("[dreamer] failed to delete child session:", error51);
167997
168407
  });
@@ -168092,19 +168502,24 @@ async function runDream(args) {
168092
168502
  if (!verifyLeaseStillHeld("before key-file identification")) {
168093
168503
  throw new Error(lostLeaseReason ?? "Dream lease lost before key-file identification");
168094
168504
  }
168095
- await identifyKeyFiles({
168096
- db: args.db,
168097
- client: args.client,
168098
- projectIdentity: args.projectIdentity,
168099
- parentSessionId,
168100
- sessionDirectory: args.sessionDirectory ?? args.projectIdentity,
168101
- holderId,
168102
- deadline,
168103
- config: args.experimentalPinKeyFiles,
168104
- fallbackModels: args.fallbackModels,
168105
- onLeaseLost: markLeaseLost,
168106
- isLeaseLost: () => lostLease
168107
- });
168505
+ const openCodeDb = openOpenCodeDb();
168506
+ if (openCodeDb) {
168507
+ try {
168508
+ await runKeyFilesTask({
168509
+ db: args.db,
168510
+ openCodeDb,
168511
+ client: args.client,
168512
+ projectPath: args.sessionDirectory ?? args.projectIdentity,
168513
+ holderId,
168514
+ deadline,
168515
+ parentSessionId,
168516
+ config: args.experimentalPinKeyFiles,
168517
+ fallbackModels: args.fallbackModels
168518
+ });
168519
+ } finally {
168520
+ closeQuietly(openCodeDb);
168521
+ }
168522
+ }
168108
168523
  if (!verifyLeaseStillHeld("after key-file identification")) {
168109
168524
  throw new Error(lostLeaseReason ?? "Dream lease lost after key-file identification");
168110
168525
  }
@@ -168322,8 +168737,7 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
168322
168737
  clearInterval(leaseInterval);
168323
168738
  if (agentSessionId) {
168324
168739
  await args.client.session.delete({
168325
- path: { id: agentSessionId },
168326
- query: { directory: args.sessionDirectory ?? args.projectIdentity }
168740
+ path: { id: agentSessionId }
168327
168741
  }).catch(() => {});
168328
168742
  }
168329
168743
  }
@@ -170463,9 +170877,34 @@ await init_read_session_chunk();
170463
170877
  // src/hooks/magic-context/transform.ts
170464
170878
  init_compartment_storage();
170465
170879
  init_project_identity();
170880
+ import * as crypto2 from "node:crypto";
170466
170881
  init_logger();
170467
170882
  await init_storage();
170468
170883
 
170884
+ // src/hooks/magic-context/boundary-execution.ts
170885
+ var FORCE_MATERIALIZE_PERCENTAGE2 = 85;
170886
+ function detectMidTurnBypassReason(input) {
170887
+ if (input.contextUsage.percentage >= FORCE_MATERIALIZE_PERCENTAGE2)
170888
+ return "force-materialize";
170889
+ if (input.historyRefreshSessions.has(input.sessionId))
170890
+ return "explicit-bust";
170891
+ if (input.sessionMeta.isSubagent)
170892
+ return "subagent";
170893
+ return "none";
170894
+ }
170895
+ function applyMidTurnDeferral(input) {
170896
+ if (input.base === "defer") {
170897
+ return { midTurnAdjustedSchedulerDecision: "defer", sideEffect: "none" };
170898
+ }
170899
+ if (input.bypassReason !== "none") {
170900
+ return { midTurnAdjustedSchedulerDecision: "execute", sideEffect: "none" };
170901
+ }
170902
+ if (input.midTurn) {
170903
+ return { midTurnAdjustedSchedulerDecision: "defer", sideEffect: "set-flag" };
170904
+ }
170905
+ return { midTurnAdjustedSchedulerDecision: "execute", sideEffect: "none" };
170906
+ }
170907
+
170469
170908
  // src/hooks/magic-context/cache-busting-signals.ts
170470
170909
  function canConsumeDeferredOnThisPass(args) {
170471
170910
  if (args.justAwaitedPublication)
@@ -170746,7 +171185,8 @@ init_read_session_formatting();
170746
171185
  init_send_session_notification();
170747
171186
  await __promiseAll([
170748
171187
  init_inject_compartments(),
170749
- init_read_session_chunk()
171188
+ init_read_session_chunk(),
171189
+ init_read_session_db()
170750
171190
  ]);
170751
171191
  // src/hooks/magic-context/sentinel.ts
170752
171192
  var WHOLE_MESSAGE_PLACEHOLDER_TEXT = "[dropped]";
@@ -171251,7 +171691,7 @@ async function runCompartmentPhase(args) {
171251
171691
  async function awaitCompartmentRun(activeRun, reason) {
171252
171692
  sessionLog(args.sessionId, reason);
171253
171693
  const timeoutMs = args.historianTimeoutMs ?? 120000;
171254
- const timeout = new Promise((resolve4) => setTimeout(() => resolve4("timeout"), timeoutMs));
171694
+ const timeout = new Promise((resolve6) => setTimeout(() => resolve6("timeout"), timeoutMs));
171255
171695
  const result = await Promise.race([activeRun.promise.then(() => "done"), timeout]);
171256
171696
  if (result === "timeout") {
171257
171697
  sessionLog(args.sessionId, `transform: compartment await timed out after ${timeoutMs}ms — proceeding without waiting`);
@@ -171383,20 +171823,38 @@ async function runCompartmentPhase(args) {
171383
171823
  // src/hooks/magic-context/transform-context-state.ts
171384
171824
  init_logger();
171385
171825
  await init_storage();
171826
+ function loadPersistedUsageWatermark(db, sessionId) {
171827
+ const result = db.prepare("SELECT last_response_time FROM session_meta WHERE session_id = ?").get(sessionId);
171828
+ if (result === null || typeof result !== "object")
171829
+ return null;
171830
+ const lastResponseTime = result.last_response_time;
171831
+ return typeof lastResponseTime === "number" ? lastResponseTime : null;
171832
+ }
171386
171833
  function loadContextUsage(contextUsageMap, db, sessionId) {
171387
- let contextUsageEntry = contextUsageMap.get(sessionId);
171388
- if (!contextUsageEntry) {
171389
- try {
171390
- const persisted = loadPersistedUsage(db, sessionId);
171391
- if (persisted) {
171392
- contextUsageMap.set(sessionId, persisted);
171393
- contextUsageEntry = persisted;
171394
- }
171395
- } catch (error51) {
171396
- sessionLog(sessionId, "transform failed loading persisted usage:", error51);
171834
+ const contextUsageEntry = contextUsageMap.get(sessionId);
171835
+ try {
171836
+ const persistedLastResponseTime = loadPersistedUsageWatermark(db, sessionId);
171837
+ const cachedLastResponseTime = contextUsageEntry?.lastResponseTime ?? contextUsageEntry?.updatedAt;
171838
+ if (contextUsageEntry && contextUsageEntry.lastResponseTime === undefined && (persistedLastResponseTime === null || persistedLastResponseTime === 0)) {
171839
+ return contextUsageEntry.usage;
171840
+ }
171841
+ if (contextUsageEntry && cachedLastResponseTime === persistedLastResponseTime) {
171842
+ return contextUsageEntry.usage;
171843
+ }
171844
+ const persisted = loadPersistedUsage(db, sessionId);
171845
+ if (persisted) {
171846
+ contextUsageMap.set(sessionId, {
171847
+ ...persisted,
171848
+ lastResponseTime: persistedLastResponseTime ?? persisted.updatedAt
171849
+ });
171850
+ return persisted.usage;
171397
171851
  }
171852
+ contextUsageMap.delete(sessionId);
171853
+ } catch (error51) {
171854
+ sessionLog(sessionId, "transform failed loading persisted usage:", error51);
171855
+ return contextUsageEntry?.usage ?? { percentage: 0, inputTokens: 0 };
171398
171856
  }
171399
- return contextUsageEntry?.usage ?? { percentage: 0, inputTokens: 0 };
171857
+ return { percentage: 0, inputTokens: 0 };
171400
171858
  }
171401
171859
  function resolveSchedulerDecision(scheduler2, sessionMeta, contextUsage, sessionId, modelKey) {
171402
171860
  try {
@@ -172590,10 +173048,10 @@ var AUTO_SEARCH_TIMEOUT_MS = 3000;
172590
173048
  async function unifiedSearchWithTimeout(db, sessionId, projectPath, prompt, options, timeoutMs) {
172591
173049
  const controller = new AbortController;
172592
173050
  let timer;
172593
- const timeoutPromise = new Promise((resolve4) => {
173051
+ const timeoutPromise = new Promise((resolve6) => {
172594
173052
  timer = setTimeout(() => {
172595
173053
  controller.abort();
172596
- resolve4(null);
173054
+ resolve6(null);
172597
173055
  }, timeoutMs);
172598
173056
  });
172599
173057
  try {
@@ -173016,7 +173474,7 @@ function isVisibleNoteReadPart(part) {
173016
173474
  }
173017
173475
 
173018
173476
  // src/hooks/magic-context/todo-view.ts
173019
- import { createHash as createHash5 } from "node:crypto";
173477
+ import { createHash as createHash6 } from "node:crypto";
173020
173478
  var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
173021
173479
  var TITLE_DONE_STATUSES = new Set(["completed"]);
173022
173480
  var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
@@ -173030,7 +173488,7 @@ function normalizeTodoStateJson(todos) {
173030
173488
  normalized.push({
173031
173489
  content: todo.content,
173032
173490
  status: todo.status,
173033
- priority: todo.priority
173491
+ priority: todo.priority ?? "medium"
173034
173492
  });
173035
173493
  }
173036
173494
  return JSON.stringify(normalized);
@@ -173061,7 +173519,7 @@ function buildSyntheticTodoPart(stateJson) {
173061
173519
  };
173062
173520
  }
173063
173521
  function computeSyntheticCallId(stateJson) {
173064
- const hash2 = createHash5("sha256").update(stateJson).digest("hex").slice(0, 16);
173522
+ const hash2 = createHash6("sha256").update(stateJson).digest("hex").slice(0, 16);
173065
173523
  return `${SYNTHETIC_CALL_ID_PREFIX}${hash2}`;
173066
173524
  }
173067
173525
  function parseTodoState(stateJson) {
@@ -173078,7 +173536,7 @@ function parseTodoState(stateJson) {
173078
173536
  result.push({
173079
173537
  content: item.content,
173080
173538
  status: item.status,
173081
- priority: item.priority
173539
+ priority: item.priority ?? "medium"
173082
173540
  });
173083
173541
  }
173084
173542
  return result;
@@ -173090,7 +173548,7 @@ function isTodoItem(value) {
173090
173548
  if (value === null || typeof value !== "object")
173091
173549
  return false;
173092
173550
  const todo = value;
173093
- return typeof todo.content === "string" && typeof todo.status === "string" && typeof todo.priority === "string";
173551
+ return typeof todo.content === "string" && typeof todo.status === "string" && (todo.priority === undefined || typeof todo.priority === "string");
173094
173552
  }
173095
173553
 
173096
173554
  // src/hooks/magic-context/transform-stage-logger.ts
@@ -173141,6 +173599,8 @@ async function runPostTransformPhase(args) {
173141
173599
  }
173142
173600
  let explicitMaterializedSuccessfully = false;
173143
173601
  let deferredMaterializedSuccessfully = false;
173602
+ let heuristicsRanSuccessfully = false;
173603
+ let pendingOpsRanSuccessfully = false;
173144
173604
  try {
173145
173605
  if (shouldApplyPendingOps) {
173146
173606
  const applyReason = isExplicitFlush ? "explicit_flush" : deferredMaterialize ? "deferred_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
@@ -173218,6 +173678,10 @@ async function runPostTransformPhase(args) {
173218
173678
  explicitMaterializedSuccessfully = true;
173219
173679
  if (deferredMaterialize)
173220
173680
  deferredMaterializedSuccessfully = true;
173681
+ heuristicsRanSuccessfully = true;
173682
+ }
173683
+ if (shouldApplyPendingOps) {
173684
+ pendingOpsRanSuccessfully = true;
173221
173685
  }
173222
173686
  } catch (error51) {
173223
173687
  sessionLog(args.sessionId, "transform failed applying pending operations:", error51);
@@ -173416,6 +173880,18 @@ async function runPostTransformPhase(args) {
173416
173880
  if (explicitMaterializedSuccessfully || deferredMaterializedSuccessfully) {
173417
173881
  args.deferredMaterializationSessions.delete(args.sessionId);
173418
173882
  }
173883
+ const workExecutedSuccessfully = explicitMaterializedSuccessfully || deferredMaterializedSuccessfully || heuristicsRanSuccessfully || pendingOpsRanSuccessfully;
173884
+ if (workExecutedSuccessfully) {
173885
+ try {
173886
+ const currentFlag = peekDeferredExecutePending(args.db, args.sessionId);
173887
+ if (currentFlag !== null) {
173888
+ const cleared = clearDeferredExecutePendingIfMatches(args.db, args.sessionId, currentFlag);
173889
+ sessionLog(args.sessionId, `[boundary-exec] deferred-execute drain: ${cleared ? "cleared" : "stale-noop"} reason=${currentFlag.reason}`);
173890
+ }
173891
+ } catch (err) {
173892
+ sessionLog(args.sessionId, `[boundary-exec] drain failed (continuing): ${err}`);
173893
+ }
173894
+ }
173419
173895
  if (args.fullFeatureMode && args.autoSearch?.enabled && args.projectPath) {
173420
173896
  const visibleMemoryIds = getVisibleMemoryIds(args.db, args.sessionId) ?? undefined;
173421
173897
  try {
@@ -173622,10 +174098,31 @@ function createTransform(deps) {
173622
174098
  }
173623
174099
  const historyBudgetTokens = resolveHistoryBudgetTokens(deps.historyBudgetPercentage, contextUsageEarly, deps.executeThresholdPercentage, deps.getModelKey?.(sessionId), deps.executeThresholdTokens);
173624
174100
  const schedulerDecisionEarly = resolveSchedulerDecision(deps.scheduler, sessionMeta, contextUsageEarly, sessionId, deps.getModelKey?.(sessionId));
174101
+ const midTurn = isMidTurn(deps, resolvedSessionId);
174102
+ const bypassReason = detectMidTurnBypassReason({
174103
+ contextUsage: contextUsageEarly,
174104
+ sessionMeta,
174105
+ historyRefreshSessions: deps.historyRefreshSessions,
174106
+ sessionId
174107
+ });
174108
+ const { midTurnAdjustedSchedulerDecision, sideEffect } = applyMidTurnDeferral({
174109
+ base: schedulerDecisionEarly,
174110
+ bypassReason,
174111
+ midTurn
174112
+ });
174113
+ if (sideEffect === "set-flag") {
174114
+ const flagPayload = {
174115
+ id: crypto2.randomUUID(),
174116
+ reason: `${schedulerDecisionEarly}-${bypassReason}`,
174117
+ recordedAt: Date.now()
174118
+ };
174119
+ setDeferredExecutePendingIfAbsent(db, sessionId, flagPayload);
174120
+ }
174121
+ sessionLog(sessionId, `[boundary-exec] base=${schedulerDecisionEarly} bypass=${bypassReason} midTurn=${midTurn} effective=${midTurnAdjustedSchedulerDecision} sideEffect=${sideEffect}`);
173625
174122
  const historyRefreshExplicitBeforePrepare = deps.historyRefreshSessions.has(sessionId);
173626
174123
  const earlyActiveRunBlocksMaterialization = (getActiveCompartmentRun(sessionId) !== undefined || sessionMeta.compartmentInProgress) && contextUsageEarly.percentage < FORCE_MATERIALIZE_PERCENTAGE;
173627
174124
  const canConsumeDeferredEarly = canConsumeDeferredOnThisPass({
173628
- schedulerDecision: schedulerDecisionEarly,
174125
+ schedulerDecision: midTurnAdjustedSchedulerDecision,
173629
174126
  contextPercentage: contextUsageEarly.percentage,
173630
174127
  justAwaitedPublication: false,
173631
174128
  activeRunBlocksMaterialization: earlyActiveRunBlocksMaterialization
@@ -173820,7 +174317,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
173820
174317
  logTransformTiming(sessionId, "stripReasoningFromMergedAssistants", tMergeStrip, `strippedParts=${strippedMergedReasoning}`);
173821
174318
  const watermark = getMaxDroppedTagNumber(db, sessionId);
173822
174319
  const contextUsage = contextUsageEarly;
173823
- const schedulerDecision = schedulerDecisionEarly;
174320
+ const schedulerDecision = midTurnAdjustedSchedulerDecision;
173824
174321
  const rawGetNotifParams = deps.getNotificationParams;
173825
174322
  const tCompartmentPhase = performance.now();
173826
174323
  const compartmentPhase = await runCompartmentPhase({
@@ -173843,7 +174340,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
173843
174340
  projectPath: projectIdentity,
173844
174341
  injectionBudgetTokens: deps.memoryConfig?.injectionBudgetTokens,
173845
174342
  getNotificationParams: rawGetNotifParams ? () => rawGetNotifParams(sessionId) : undefined,
173846
- safeForBackgroundCompression: isCacheBusting || schedulerDecisionEarly === "execute",
174343
+ safeForBackgroundCompression: isCacheBusting || midTurnAdjustedSchedulerDecision === "execute",
173847
174344
  suppressBackgroundCompressionThisPass: historyBustThisPass,
173848
174345
  deferredHistoryRefreshSessions,
173849
174346
  skipAwaitForThisPass: skipCompartmentAwaitForThisPass,
@@ -173868,7 +174365,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
173868
174365
  logTransformTiming(sessionId, "compartmentPhase", tCompartmentPhase);
173869
174366
  const lateActiveRunBlocksMaterialization = getActiveCompartmentRun(sessionId) !== undefined && contextUsageEarly.percentage < FORCE_MATERIALIZE_PERCENTAGE;
173870
174367
  const canConsumeDeferredLate = canConsumeDeferredOnThisPass({
173871
- schedulerDecision: schedulerDecisionEarly,
174368
+ schedulerDecision: midTurnAdjustedSchedulerDecision,
173872
174369
  contextPercentage: contextUsageEarly.percentage,
173873
174370
  justAwaitedPublication: compartmentPhase.justAwaitedPublication,
173874
174371
  activeRunBlocksMaterialization: lateActiveRunBlocksMaterialization
@@ -174237,7 +174734,8 @@ function createEventHandler2(deps) {
174237
174734
  percentage,
174238
174735
  inputTokens: totalInputTokens
174239
174736
  },
174240
- updatedAt: now
174737
+ updatedAt: now,
174738
+ lastResponseTime: now
174241
174739
  });
174242
174740
  updates.lastContextPercentage = percentage;
174243
174741
  updates.lastInputTokens = totalInputTokens;
@@ -174741,9 +175239,9 @@ function createToolExecuteAfterHook(args) {
174741
175239
  init_send_session_notification();
174742
175240
 
174743
175241
  // src/hooks/magic-context/system-prompt-hash.ts
174744
- import { createHash as createHash6 } from "node:crypto";
174745
- import { existsSync as existsSync13, readFileSync as readFileSync9, realpathSync } from "node:fs";
174746
- import { join as join21, resolve as resolve4, sep } from "node:path";
175242
+ import { createHash as createHash7 } from "node:crypto";
175243
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
175244
+ import { join as join25 } from "node:path";
174747
175245
 
174748
175246
  // src/agents/magic-context-prompt.ts
174749
175247
  function getToolHistoryGuidance(dropToolStructure) {
@@ -174833,19 +175331,146 @@ Prefer many small targeted operations over one large blanket operation. Compress
174833
175331
  }
174834
175332
 
174835
175333
  // src/hooks/magic-context/system-prompt-hash.ts
175334
+ init_logger();
175335
+ await init_storage();
175336
+
175337
+ // src/hooks/magic-context/key-files-block.ts
174836
175338
  init_compartment_storage();
175339
+ import { readFileSync as readFileSync12, realpathSync as realpathSync3 } from "node:fs";
175340
+ import { join as join24, sep as sep2 } from "node:path";
175341
+ init_project_key_files();
174837
175342
  init_logger();
175343
+ var cachedKeyFilesBySession = new Map;
175344
+ var staleUpdates = new Map;
175345
+ function staleKey(update) {
175346
+ return `${update.projectPath}\x00${update.path}\x00${update.generatedAtWitness}\x00${update.staleReason}`;
175347
+ }
175348
+ function queueStaleUpdate(projectPath, path5, generatedAtWitness, staleReason) {
175349
+ const update = { projectPath, path: path5, generatedAtWitness, staleReason };
175350
+ staleUpdates.set(staleKey(update), update);
175351
+ }
175352
+ function flushStaleUpdates(db) {
175353
+ if (staleUpdates.size === 0)
175354
+ return 0;
175355
+ const updates = [...staleUpdates.values()];
175356
+ staleUpdates.clear();
175357
+ const stmt = db.prepare(`UPDATE project_key_files
175358
+ SET stale_reason = ?1
175359
+ WHERE project_path = ?2
175360
+ AND path = ?3
175361
+ AND generated_at = ?4
175362
+ AND (stale_reason IS NULL OR stale_reason != ?1)`);
175363
+ let changed = 0;
175364
+ for (const update of updates) {
175365
+ try {
175366
+ changed += stmt.run(update.staleReason, update.projectPath, update.path, update.generatedAtWitness).changes;
175367
+ } catch (error51) {
175368
+ log("[key-files] flushStaleUpdates failed:", error51);
175369
+ }
175370
+ }
175371
+ return changed;
175372
+ }
175373
+ function clearKeyFilesCacheForSession(sessionId) {
175374
+ cachedKeyFilesBySession.delete(sessionId);
175375
+ }
175376
+ function isUnderProject(projectPath, absPath) {
175377
+ const root = realpathSync3(projectPath);
175378
+ return absPath.startsWith(root + sep2) || absPath === root;
175379
+ }
175380
+ function buildKeyFilesBlock(db, projectPath, config2 = { enabled: true, tokenBudget: 1e4 }) {
175381
+ if (!config2.enabled)
175382
+ return null;
175383
+ if (!isAftAvailable())
175384
+ return null;
175385
+ const rows = readCurrentKeyFiles(db, projectPath);
175386
+ if (rows.length === 0)
175387
+ return null;
175388
+ for (const row of rows) {
175389
+ if (row.staleReason !== null)
175390
+ continue;
175391
+ let nextStale = null;
175392
+ let observed = false;
175393
+ try {
175394
+ const absPath = join24(projectPath, row.path);
175395
+ const real = realpathSync3(absPath);
175396
+ if (!isUnderProject(projectPath, real)) {
175397
+ nextStale = "missing";
175398
+ observed = true;
175399
+ } else {
175400
+ const diskHash = sha256(readFileSync12(real));
175401
+ if (diskHash !== row.contentHash)
175402
+ nextStale = "content_drift";
175403
+ observed = true;
175404
+ }
175405
+ } catch (error51) {
175406
+ const code = error51?.code;
175407
+ if (code === "ENOENT" || code === "ELOOP") {
175408
+ nextStale = "missing";
175409
+ observed = true;
175410
+ } else {
175411
+ log(`[key-files] freshness check transient failure: ${row.path}: ${error51 instanceof Error ? error51.message : String(error51)}`);
175412
+ }
175413
+ }
175414
+ if (observed && nextStale !== null) {
175415
+ queueStaleUpdate(row.projectPath, row.path, row.generatedAt, nextStale);
175416
+ }
175417
+ }
175418
+ const blocks = [];
175419
+ let used = 0;
175420
+ for (const row of rows) {
175421
+ if (used + row.localTokenEstimate > config2.tokenBudget)
175422
+ break;
175423
+ blocks.push(` <key-file path="${escapeXmlAttr(row.path)}">
175424
+ ${escapeXmlContent(row.content)}
175425
+ </key-file>`);
175426
+ used += row.localTokenEstimate;
175427
+ }
175428
+ if (blocks.length === 0)
175429
+ return null;
175430
+ const rendered = `<key-files>
175431
+ ${blocks.join(`
175432
+ `)}
175433
+ </key-files>`;
175434
+ flushStaleUpdates(db);
175435
+ return rendered;
175436
+ }
175437
+ function readVersionedKeyFiles(args) {
175438
+ const config2 = args.config ?? { enabled: true, tokenBudget: 1e4 };
175439
+ if (args.sessionMeta.isSubagent)
175440
+ return null;
175441
+ if (!config2.enabled)
175442
+ return null;
175443
+ if (!isAftAvailable())
175444
+ return null;
175445
+ const projectPath = resolveProjectPath(args.directory ?? args.sessionMeta.sessionId);
175446
+ if (!projectPath)
175447
+ return null;
175448
+ const currentVersion = getKeyFilesVersion(args.db, projectPath);
175449
+ if (args.sessionId) {
175450
+ const cached2 = cachedKeyFilesBySession.get(args.sessionId);
175451
+ if (cached2 && !args.isCacheBusting && cached2.version === currentVersion) {
175452
+ return cached2.value;
175453
+ }
175454
+ }
175455
+ const value = buildKeyFilesBlock(args.db, projectPath, config2);
175456
+ if (args.sessionId) {
175457
+ cachedKeyFilesBySession.set(args.sessionId, { value, version: currentVersion });
175458
+ if (value)
175459
+ sessionLog(args.sessionId, `loaded key-files block (v${currentVersion}, ${value.length} chars)`);
175460
+ }
175461
+ return value;
175462
+ }
175463
+
175464
+ // src/hooks/magic-context/system-prompt-hash.ts
174838
175465
  init_read_session_formatting();
174839
- await init_storage();
174840
175466
  var MAGIC_CONTEXT_MARKER = "## Magic Context";
174841
175467
  var PROJECT_DOCS_MARKER = "<project-docs>";
174842
175468
  var USER_PROFILE_MARKER = "<user-profile>";
174843
175469
  var KEY_FILES_MARKER = "<key-files>";
174844
175470
  var cachedUserProfileBySession = new Map;
174845
- var cachedKeyFilesBySession = new Map;
174846
175471
  function clearSystemPromptHashSession(sessionId, handleMaps) {
174847
175472
  cachedUserProfileBySession.delete(sessionId);
174848
- cachedKeyFilesBySession.delete(sessionId);
175473
+ clearKeyFilesCacheForSession(sessionId);
174849
175474
  handleMaps.stickyDateBySession.delete(sessionId);
174850
175475
  handleMaps.cachedDocsBySession.delete(sessionId);
174851
175476
  }
@@ -174856,10 +175481,10 @@ var DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
174856
175481
  function readProjectDocs(directory) {
174857
175482
  const sections = [];
174858
175483
  for (const filename of DOC_FILES) {
174859
- const filePath = join21(directory, filename);
175484
+ const filePath = join25(directory, filename);
174860
175485
  try {
174861
- if (existsSync13(filePath)) {
174862
- const content = readFileSync9(filePath, "utf-8").trim();
175486
+ if (existsSync15(filePath)) {
175487
+ const content = readFileSync13(filePath, "utf-8").trim();
174863
175488
  if (content.length > 0) {
174864
175489
  sections.push(`<${filename}>
174865
175490
  ${content}
@@ -174956,71 +175581,20 @@ ${items}
174956
175581
  output.system.push(profileBlock);
174957
175582
  }
174958
175583
  }
174959
- if (deps.experimentalPinKeyFiles && !isSubagentSession) {
174960
- const hasCachedKeyFiles = cachedKeyFilesBySession.has(sessionId);
174961
- if (!hasCachedKeyFiles || isCacheBusting) {
174962
- const keyFileEntries = getKeyFiles(deps.db, sessionId);
174963
- if (keyFileEntries.length > 0) {
174964
- const sections = [];
174965
- const projectRoot = resolve4(deps.directory);
174966
- let remainingBudgetTokens = deps.experimentalPinKeyFilesTokenBudget ?? 1e4;
174967
- for (const entry of keyFileEntries) {
174968
- try {
174969
- const absPath = resolve4(deps.directory, entry.filePath);
174970
- if (!absPath.startsWith(projectRoot + sep) && absPath !== projectRoot) {
174971
- log(`[magic-context] key file path escapes project root, skipping: ${entry.filePath}`);
174972
- continue;
174973
- }
174974
- if (!existsSync13(absPath))
174975
- continue;
174976
- let realPath;
174977
- try {
174978
- realPath = realpathSync(absPath);
174979
- } catch {
174980
- continue;
174981
- }
174982
- if (!realPath.startsWith(projectRoot + sep) && realPath !== projectRoot) {
174983
- log(`[magic-context] key file symlink escapes project root, skipping: ${entry.filePath} → ${realPath}`);
174984
- continue;
174985
- }
174986
- const content = readFileSync9(realPath, "utf-8").trim();
174987
- if (content.length === 0)
174988
- continue;
174989
- const fileTokens = estimateTokens(content);
174990
- if (fileTokens > remainingBudgetTokens) {
174991
- log(`[magic-context] key file ${entry.filePath} exceeds remaining budget (${fileTokens} > ${remainingBudgetTokens}), skipping`);
174992
- continue;
174993
- }
174994
- remainingBudgetTokens -= fileTokens;
174995
- sections.push(`<file path="${escapeXmlAttr(entry.filePath)}">
174996
- ${escapeXmlContent(content)}
174997
- </file>`);
174998
- } catch (error51) {
174999
- log(`[magic-context] failed to read key file ${entry.filePath}:`, error51);
175000
- }
175001
- }
175002
- if (sections.length > 0) {
175003
- cachedKeyFilesBySession.set(sessionId, `${KEY_FILES_MARKER}
175004
- ${sections.join(`
175005
-
175006
- `)}
175007
- </key-files>`);
175008
- if (!hasCachedKeyFiles) {
175009
- sessionLog(sessionId, `loaded ${sections.length} key file(s) into system prompt`);
175010
- } else {
175011
- sessionLog(sessionId, "refreshed key files (cache-busting pass)");
175012
- }
175013
- } else {
175014
- cachedKeyFilesBySession.set(sessionId, null);
175015
- }
175016
- } else {
175017
- cachedKeyFilesBySession.set(sessionId, null);
175584
+ if (deps.experimentalPinKeyFiles && sessionMetaEarly) {
175585
+ const keyFilesBlock = readVersionedKeyFiles({
175586
+ db: deps.db,
175587
+ sessionId,
175588
+ sessionMeta: sessionMetaEarly,
175589
+ directory: deps.directory,
175590
+ isCacheBusting,
175591
+ config: {
175592
+ enabled: deps.experimentalPinKeyFiles,
175593
+ tokenBudget: deps.experimentalPinKeyFilesTokenBudget ?? 1e4
175018
175594
  }
175019
- }
175020
- const keyFilesBlock = cachedKeyFilesBySession.get(sessionId);
175021
- if (keyFilesBlock && !fullPrompt.includes(KEY_FILES_MARKER)) {
175595
+ });
175596
+ if (keyFilesBlock && !fullPrompt.includes(KEY_FILES_MARKER))
175022
175597
  output.system.push(keyFilesBlock);
175023
- }
175024
175598
  }
175025
175599
  const DATE_PATTERN = /Today's date: .+/;
175026
175600
  for (let i = 0;i < output.system.length; i++) {
@@ -175046,7 +175620,7 @@ ${sections.join(`
175046
175620
  `);
175047
175621
  if (systemContent.length === 0)
175048
175622
  return;
175049
- const currentHash = createHash6("md5").update(systemContent).digest("hex");
175623
+ const currentHash = createHash7("md5").update(systemContent).digest("hex");
175050
175624
  if (!sessionMetaEarly) {
175051
175625
  return;
175052
175626
  }
@@ -177021,7 +177595,7 @@ function createToolRegistry(args) {
177021
177595
  console.warn(`[magic-context] embedding model changed from ${storedModelId} to ${currentModelId}; cleared embeddings for project ${launchProjectPath}`);
177022
177596
  }
177023
177597
  }
177024
- const resolveProjectPath = (directory) => resolveProjectIdentity(directory);
177598
+ const resolveProjectPath2 = (directory) => resolveProjectIdentity(directory);
177025
177599
  const ctxReduceEnabled = pluginConfig.ctx_reduce_enabled !== false;
177026
177600
  const allTools = {
177027
177601
  ...ctxReduceEnabled ? createCtxReduceTools({
@@ -177032,11 +177606,11 @@ function createToolRegistry(args) {
177032
177606
  ...createCtxNoteTools({
177033
177607
  db,
177034
177608
  dreamerEnabled: pluginConfig.dreamer?.enabled === true,
177035
- resolveProjectPath
177609
+ resolveProjectPath: resolveProjectPath2
177036
177610
  }),
177037
177611
  ...createCtxSearchTools({
177038
177612
  db,
177039
- resolveProjectPath,
177613
+ resolveProjectPath: resolveProjectPath2,
177040
177614
  memoryEnabled,
177041
177615
  embeddingEnabled: embeddingConfig2.provider !== "off",
177042
177616
  gitCommitsEnabled: pluginConfig.experimental?.git_commit_indexing?.enabled === true
@@ -177044,7 +177618,7 @@ function createToolRegistry(args) {
177044
177618
  ...memoryEnabled ? {
177045
177619
  ...createCtxMemoryTools({
177046
177620
  db,
177047
- resolveProjectPath,
177621
+ resolveProjectPath: resolveProjectPath2,
177048
177622
  memoryEnabled: true,
177049
177623
  embeddingEnabled: embeddingConfig2.provider !== "off",
177050
177624
  allowedActions: ["write", "delete"]
@@ -177069,7 +177643,7 @@ init_logger();
177069
177643
  import {
177070
177644
  mkdirSync as mkdirSync8,
177071
177645
  readdirSync,
177072
- readFileSync as readFileSync10,
177646
+ readFileSync as readFileSync14,
177073
177647
  renameSync as renameSync2,
177074
177648
  unlinkSync as unlinkSync3,
177075
177649
  writeFileSync as writeFileSync7
@@ -177078,17 +177652,17 @@ import { createServer } from "node:http";
177078
177652
  import { dirname as dirname7 } from "node:path";
177079
177653
 
177080
177654
  // src/shared/rpc-utils.ts
177081
- import { createHash as createHash7 } from "node:crypto";
177082
- import { join as join22 } from "node:path";
177655
+ import { createHash as createHash8 } from "node:crypto";
177656
+ import { join as join26 } from "node:path";
177083
177657
  function projectHash(directory) {
177084
177658
  const normalized = directory.replace(/\/+$/, "");
177085
- return createHash7("sha256").update(normalized).digest("hex").slice(0, 16);
177659
+ return createHash8("sha256").update(normalized).digest("hex").slice(0, 16);
177086
177660
  }
177087
177661
  function rpcPortDir(storageDir, directory) {
177088
- return join22(storageDir, "rpc", projectHash(directory));
177662
+ return join26(storageDir, "rpc", projectHash(directory));
177089
177663
  }
177090
177664
  function rpcPortFilePath(storageDir, directory, pid = process.pid) {
177091
- return join22(rpcPortDir(storageDir, directory), `port-${pid}.json`);
177665
+ return join26(rpcPortDir(storageDir, directory), `port-${pid}.json`);
177092
177666
  }
177093
177667
  function isPidAlive(pid) {
177094
177668
  if (!Number.isInteger(pid) || pid <= 0)
@@ -177146,7 +177720,7 @@ class MagicContextRpcServer {
177146
177720
  this.handlers.set(method, handler);
177147
177721
  }
177148
177722
  async start() {
177149
- return new Promise((resolve5, reject) => {
177723
+ return new Promise((resolve6, reject) => {
177150
177724
  const server = createServer((req, res) => this.dispatch(req, res));
177151
177725
  server.on("error", (err) => {
177152
177726
  log(`[rpc] server error: ${err.message}`);
@@ -177175,7 +177749,7 @@ class MagicContextRpcServer {
177175
177749
  } catch (err) {
177176
177750
  log(`[rpc] failed to write port file: ${err}`);
177177
177751
  }
177178
- resolve5(this.port);
177752
+ resolve6(this.port);
177179
177753
  });
177180
177754
  server.unref();
177181
177755
  });
@@ -177185,7 +177759,7 @@ class MagicContextRpcServer {
177185
177759
  for (const entry of readdirSync(this.portDir)) {
177186
177760
  if (!entry.startsWith("port-") || !entry.endsWith(".json"))
177187
177761
  continue;
177188
- const record2 = parseRpcPortFile(readFileSync10(`${this.portDir}/${entry}`, "utf-8"));
177762
+ const record2 = parseRpcPortFile(readFileSync14(`${this.portDir}/${entry}`, "utf-8"));
177189
177763
  if (!record2 || record2.pid === process.pid || !isPidAlive(record2.pid))
177190
177764
  continue;
177191
177765
  log(`[rpc] another Magic Context RPC server is active for this project (pid ${record2.pid}, port ${record2.port}); starting separate instance on a new port`);