@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.
- package/README.md +2 -2
- package/dist/features/magic-context/dreamer/lease.d.ts +1 -0
- package/dist/features/magic-context/dreamer/lease.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
- package/dist/features/magic-context/key-files/aft-availability.d.ts +11 -0
- package/dist/features/magic-context/key-files/aft-availability.d.ts.map +1 -0
- package/dist/features/magic-context/key-files/identify-key-files.d.ts +45 -0
- package/dist/features/magic-context/key-files/identify-key-files.d.ts.map +1 -1
- package/dist/features/magic-context/key-files/project-key-files.d.ts +42 -0
- package/dist/features/magic-context/key-files/project-key-files.d.ts.map +1 -0
- package/dist/features/magic-context/key-files/read-history.d.ts +26 -0
- package/dist/features/magic-context/key-files/read-history.d.ts.map +1 -0
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/overflow-detection.d.ts +1 -1
- package/dist/features/magic-context/sidekick/agent.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +1 -0
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +8 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta.d.ts +1 -1
- package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -1
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/cache.d.ts.map +1 -1
- package/dist/hooks/magic-context/boundary-execution.d.ts +24 -0
- package/dist/hooks/magic-context/boundary-execution.d.ts.map +1 -0
- package/dist/hooks/magic-context/event-handler.d.ts +1 -0
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/key-files-block.d.ts +27 -0
- package/dist/hooks/magic-context/key-files-block.d.ts.map +1 -0
- package/dist/hooks/magic-context/read-session-db.d.ts +2 -0
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-context-state.d.ts +5 -2
- package/dist/hooks/magic-context/transform-context-state.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +1 -0
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/index.js +1215 -641
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/shared/transcript.d.ts +2 -2
- package/package.json +1 -1
- 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
|
|
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
|
|
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
|
|
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((
|
|
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 =
|
|
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 =
|
|
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((
|
|
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
|
|
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 =
|
|
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
|
|
160383
|
-
import { join as
|
|
160558
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
160559
|
+
import { join as join17 } from "node:path";
|
|
160384
160560
|
function resolveOpencodeDbPath() {
|
|
160385
|
-
return
|
|
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 (!
|
|
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
|
|
160631
|
-
import { join as
|
|
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:
|
|
160810
|
+
return { dbDir, dbPath: join18(dbDir, "context.db") };
|
|
160635
160811
|
}
|
|
160636
160812
|
function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
|
|
160637
|
-
if (
|
|
160813
|
+
if (existsSync13(targetDbPath))
|
|
160638
160814
|
return;
|
|
160639
160815
|
const legacyDir = getLegacyOpenCodeMagicContextStorageDir();
|
|
160640
|
-
const legacyDbPath =
|
|
160641
|
-
if (!
|
|
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 =
|
|
160648
|
-
if (
|
|
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 =
|
|
160657
|
-
const targetModelsDir =
|
|
160658
|
-
if (
|
|
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
|
-
|
|
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
|
|
162063
|
-
import { existsSync as
|
|
162064
|
-
import { homedir as
|
|
162065
|
-
import { join as
|
|
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
|
|
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
|
|
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" ?
|
|
162081
|
-
const jsonc =
|
|
162082
|
-
if (
|
|
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 =
|
|
162085
|
-
if (
|
|
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 (
|
|
162356
|
+
if (existsSync14(modelsJsonPath)) {
|
|
162118
162357
|
fileFound = true;
|
|
162119
|
-
const raw =
|
|
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 &&
|
|
162135
|
-
const config2 = parseJsonc(
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 }
|
|
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((
|
|
163026
|
-
setTimeout(
|
|
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 =
|
|
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
|
|
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 =
|
|
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 }
|
|
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
|
|
165570
|
-
import { dirname as dirname8, join as
|
|
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 =
|
|
165585
|
-
const jsonPath =
|
|
165586
|
-
if (
|
|
165823
|
+
const jsoncPath = join27(configDir, "tui.jsonc");
|
|
165824
|
+
const jsonPath = join27(configDir, "tui.json");
|
|
165825
|
+
if (existsSync16(jsoncPath))
|
|
165587
165826
|
return jsoncPath;
|
|
165588
|
-
if (
|
|
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 (
|
|
165597
|
-
const raw =
|
|
165598
|
-
config2 =
|
|
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, `${
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
167155
|
-
import { join as
|
|
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/
|
|
167161
|
-
|
|
167162
|
-
|
|
167163
|
-
|
|
167164
|
-
|
|
167165
|
-
|
|
167166
|
-
|
|
167167
|
-
|
|
167168
|
-
|
|
167169
|
-
|
|
167170
|
-
|
|
167171
|
-
|
|
167172
|
-
|
|
167173
|
-
|
|
167174
|
-
|
|
167175
|
-
|
|
167176
|
-
|
|
167177
|
-
|
|
167178
|
-
|
|
167179
|
-
|
|
167180
|
-
|
|
167181
|
-
|
|
167182
|
-
|
|
167183
|
-
|
|
167184
|
-
|
|
167185
|
-
|
|
167186
|
-
|
|
167187
|
-
|
|
167188
|
-
|
|
167189
|
-
|
|
167190
|
-
|
|
167191
|
-
|
|
167192
|
-
|
|
167193
|
-
|
|
167194
|
-
|
|
167195
|
-
|
|
167196
|
-
|
|
167197
|
-
|
|
167198
|
-
|
|
167199
|
-
|
|
167200
|
-
|
|
167201
|
-
|
|
167202
|
-
|
|
167203
|
-
|
|
167204
|
-
|
|
167205
|
-
|
|
167206
|
-
|
|
167207
|
-
|
|
167208
|
-
|
|
167209
|
-
|
|
167210
|
-
|
|
167211
|
-
|
|
167212
|
-
|
|
167213
|
-
|
|
167214
|
-
|
|
167215
|
-
|
|
167216
|
-
|
|
167217
|
-
|
|
167218
|
-
|
|
167219
|
-
|
|
167220
|
-
|
|
167221
|
-
|
|
167222
|
-
|
|
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/
|
|
167227
|
-
|
|
167228
|
-
|
|
167229
|
-
|
|
167230
|
-
|
|
167231
|
-
|
|
167232
|
-
|
|
167233
|
-
|
|
167234
|
-
|
|
167235
|
-
|
|
167236
|
-
|
|
167237
|
-
|
|
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
|
|
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
|
-
|
|
167244
|
-
|
|
167245
|
-
}
|
|
167246
|
-
|
|
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
|
|
167250
|
-
const
|
|
167251
|
-
|
|
167252
|
-
|
|
167253
|
-
|
|
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
|
-
|
|
167577
|
+
const rel = normalizeProjectRelativePath(args.projectPath, row.file_path);
|
|
167578
|
+
if (!rel)
|
|
167256
167579
|
continue;
|
|
167257
|
-
|
|
167258
|
-
|
|
167259
|
-
|
|
167260
|
-
|
|
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
|
|
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
|
-
|
|
167268
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
167281
|
-
|
|
167282
|
-
|
|
167283
|
-
- Test files, scripts, or generated files
|
|
167680
|
+
| path | total reads | last read | common line ranges |
|
|
167681
|
+
| --- | ---: | --- | --- |
|
|
167682
|
+
${formatCandidates(args.candidates)}
|
|
167284
167683
|
|
|
167285
|
-
|
|
167684
|
+
## Current key files (if any)
|
|
167286
167685
|
|
|
167287
|
-
|
|
167288
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
167301
|
-
|
|
167302
|
-
|
|
167303
|
-
|
|
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
|
-
|
|
167306
|
-
|
|
167307
|
-
|
|
167308
|
-
|
|
167309
|
-
|
|
167310
|
-
|
|
167311
|
-
|
|
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
|
|
167572
|
-
return
|
|
168166
|
+
function getOpenCodeDbPath() {
|
|
168167
|
+
return join14(getDataDir(), "opencode", "opencode.db");
|
|
167573
168168
|
}
|
|
167574
168169
|
function openOpenCodeDb() {
|
|
167575
|
-
const dbPath =
|
|
167576
|
-
if (!
|
|
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:
|
|
167892
|
-
structure:
|
|
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
|
-
|
|
168096
|
-
|
|
168097
|
-
|
|
168098
|
-
|
|
168099
|
-
|
|
168100
|
-
|
|
168101
|
-
|
|
168102
|
-
|
|
168103
|
-
|
|
168104
|
-
|
|
168105
|
-
|
|
168106
|
-
|
|
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((
|
|
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
|
-
|
|
171388
|
-
|
|
171389
|
-
|
|
171390
|
-
|
|
171391
|
-
|
|
171392
|
-
|
|
171393
|
-
|
|
171394
|
-
|
|
171395
|
-
|
|
171396
|
-
|
|
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
|
|
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((
|
|
173051
|
+
const timeoutPromise = new Promise((resolve6) => {
|
|
172594
173052
|
timer = setTimeout(() => {
|
|
172595
173053
|
controller.abort();
|
|
172596
|
-
|
|
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
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 ||
|
|
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:
|
|
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
|
|
174745
|
-
import { existsSync as
|
|
174746
|
-
import { join as
|
|
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
|
-
|
|
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 =
|
|
175484
|
+
const filePath = join25(directory, filename);
|
|
174860
175485
|
try {
|
|
174861
|
-
if (
|
|
174862
|
-
const content =
|
|
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 &&
|
|
174960
|
-
const
|
|
174961
|
-
|
|
174962
|
-
|
|
174963
|
-
|
|
174964
|
-
|
|
174965
|
-
|
|
174966
|
-
|
|
174967
|
-
|
|
174968
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
177082
|
-
import { join as
|
|
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
|
|
177659
|
+
return createHash8("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
177086
177660
|
}
|
|
177087
177661
|
function rpcPortDir(storageDir, directory) {
|
|
177088
|
-
return
|
|
177662
|
+
return join26(storageDir, "rpc", projectHash(directory));
|
|
177089
177663
|
}
|
|
177090
177664
|
function rpcPortFilePath(storageDir, directory, pid = process.pid) {
|
|
177091
|
-
return
|
|
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((
|
|
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
|
-
|
|
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(
|
|
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`);
|