@cortexkit/pi-magic-context 0.18.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.
@@ -11277,7 +11277,7 @@ function finalize(ctx, schema) {
11277
11277
  result.$schema = "http://json-schema.org/draft-07/schema#";
11278
11278
  } else if (ctx.target === "draft-04") {
11279
11279
  result.$schema = "http://json-schema.org/draft-04/schema#";
11280
- } else if (ctx.target === "openapi-3.0") {} else {}
11280
+ } else if (ctx.target === "openapi-3.0") {}
11281
11281
  if (ctx.external?.uri) {
11282
11282
  const id = ctx.external.registry.get(schema)?.id;
11283
11283
  if (!id)
@@ -11538,7 +11538,7 @@ var formatMap, stringProcessor = (schema, ctx, _json, _params) => {
11538
11538
  if (val === undefined) {
11539
11539
  if (ctx.unrepresentable === "throw") {
11540
11540
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
11541
- } else {}
11541
+ }
11542
11542
  } else if (typeof val === "bigint") {
11543
11543
  if (ctx.unrepresentable === "throw") {
11544
11544
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -14891,10 +14891,63 @@ var init_magic_context = __esm(() => {
14891
14891
  });
14892
14892
  });
14893
14893
 
14894
- // ../plugin/src/shared/logger.ts
14895
- import * as fs from "node:fs";
14894
+ // ../plugin/src/shared/harness.ts
14895
+ function setHarness(value) {
14896
+ if (harnessLocked && currentHarness !== value) {
14897
+ throw new Error(`Magic Context: harness already locked to "${currentHarness}"; cannot change to "${value}"`);
14898
+ }
14899
+ currentHarness = value;
14900
+ harnessLocked = true;
14901
+ }
14902
+ function getHarness() {
14903
+ return currentHarness;
14904
+ }
14905
+ var currentHarness = "opencode", harnessLocked = false;
14906
+
14907
+ // ../plugin/src/shared/data-path.ts
14896
14908
  import * as os from "node:os";
14897
14909
  import * as path from "node:path";
14910
+ function getDataDir() {
14911
+ return process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
14912
+ }
14913
+ function getMagicContextTempDir(harness = getHarness()) {
14914
+ return path.join(os.tmpdir(), harness, "magic-context");
14915
+ }
14916
+ function getMagicContextLogPath(harness = getHarness()) {
14917
+ return path.join(getMagicContextTempDir(harness), "magic-context.log");
14918
+ }
14919
+ function getProjectMagicContextDir(directory) {
14920
+ return path.join(directory, ".opencode", "magic-context");
14921
+ }
14922
+ function getProjectMagicContextHistorianDir(directory) {
14923
+ return path.join(getProjectMagicContextDir(directory), "historian");
14924
+ }
14925
+ function getOpenCodeStorageDir() {
14926
+ return path.join(getDataDir(), "opencode", "storage");
14927
+ }
14928
+ function getMagicContextStorageDir() {
14929
+ return path.join(getDataDir(), "cortexkit", "magic-context");
14930
+ }
14931
+ function getLegacyOpenCodeMagicContextStorageDir() {
14932
+ return path.join(getOpenCodeStorageDir(), "plugin", "magic-context");
14933
+ }
14934
+ function getCacheDir() {
14935
+ return process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), ".cache");
14936
+ }
14937
+ var init_data_path = () => {};
14938
+
14939
+ // ../plugin/src/shared/logger.ts
14940
+ import * as fs from "node:fs";
14941
+ import * as path2 from "node:path";
14942
+ function ensureDir(filePath) {
14943
+ const dir = path2.dirname(filePath);
14944
+ if (dir === lastEnsuredDir)
14945
+ return;
14946
+ try {
14947
+ fs.mkdirSync(dir, { recursive: true });
14948
+ lastEnsuredDir = dir;
14949
+ } catch {}
14950
+ }
14898
14951
  function flush() {
14899
14952
  if (flushTimer) {
14900
14953
  clearTimeout(flushTimer);
@@ -14905,6 +14958,8 @@ function flush() {
14905
14958
  const data = buffer.join("");
14906
14959
  buffer = [];
14907
14960
  try {
14961
+ const logFile = getMagicContextLogPath();
14962
+ ensureDir(logFile);
14908
14963
  fs.appendFileSync(logFile, data);
14909
14964
  } catch {}
14910
14965
  }
@@ -14935,9 +14990,9 @@ ${data.stack}` : ""}` : ` ${JSON.stringify(data)}`;
14935
14990
  function sessionLog(sessionId, message, data) {
14936
14991
  log(`[magic-context][${sessionId}] ${message}`, data);
14937
14992
  }
14938
- var logFile, isTestEnv = false, buffer, flushTimer = null, FLUSH_INTERVAL_MS = 500, BUFFER_SIZE_LIMIT = 50;
14993
+ var isTestEnv = false, buffer, flushTimer = null, FLUSH_INTERVAL_MS = 500, BUFFER_SIZE_LIMIT = 50, lastEnsuredDir = null;
14939
14994
  var init_logger = __esm(() => {
14940
- logFile = path.join(os.tmpdir(), "magic-context.log");
14995
+ init_data_path();
14941
14996
  buffer = [];
14942
14997
  if (!isTestEnv) {
14943
14998
  process.on("exit", flush);
@@ -14961,30 +15016,47 @@ function cosineSimilarity(a, b) {
14961
15016
  return denominator === 0 ? 0 : dotProduct / denominator;
14962
15017
  }
14963
15018
 
14964
- // ../plugin/src/shared/data-path.ts
14965
- import * as os2 from "node:os";
14966
- import * as path2 from "node:path";
14967
- function getDataDir() {
14968
- return process.env.XDG_DATA_HOME ?? path2.join(os2.homedir(), ".local", "share");
14969
- }
14970
- function getOpenCodeStorageDir() {
14971
- return path2.join(getDataDir(), "opencode", "storage");
15019
+ // ../plugin/src/features/magic-context/memory/normalize-hash.ts
15020
+ import { createHash } from "node:crypto";
15021
+ function normalizeMemoryContent(content) {
15022
+ return content.toLowerCase().replace(/\s+/g, " ").trim();
14972
15023
  }
14973
- function getMagicContextStorageDir() {
14974
- return path2.join(getDataDir(), "cortexkit", "magic-context");
15024
+ function computeNormalizedHash(content) {
15025
+ const normalized = normalizeMemoryContent(content);
15026
+ return createHash("md5").update(normalized).digest("hex");
14975
15027
  }
14976
- function getLegacyOpenCodeMagicContextStorageDir() {
14977
- return path2.join(getOpenCodeStorageDir(), "plugin", "magic-context");
15028
+ var init_normalize_hash = () => {};
15029
+
15030
+ // ../plugin/src/features/magic-context/memory/embedding-identity.ts
15031
+ function normalizeEndpoint(endpoint) {
15032
+ return endpoint?.trim().replace(/\/+$/, "") ?? "";
14978
15033
  }
14979
- function getCacheDir() {
14980
- return process.env.XDG_CACHE_HOME ?? path2.join(os2.homedir(), ".cache");
15034
+ function getEmbeddingProviderIdentity(config2) {
15035
+ if (config2.provider === "off") {
15036
+ return "embedding-provider:off";
15037
+ }
15038
+ const identityInput = config2.provider === "openai-compatible" ? {
15039
+ provider: "openai-compatible",
15040
+ model: config2.model.trim(),
15041
+ endpoint: normalizeEndpoint(config2.endpoint),
15042
+ apiKeyPresent: Boolean(config2.api_key?.trim())
15043
+ } : {
15044
+ provider: "local",
15045
+ model: config2.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
15046
+ endpoint: "",
15047
+ apiKeyPresent: false
15048
+ };
15049
+ return `embedding-provider:${computeNormalizedHash(JSON.stringify(identityInput))}`;
14981
15050
  }
14982
- var init_data_path = () => {};
15051
+ var init_embedding_identity = __esm(() => {
15052
+ init_magic_context();
15053
+ init_normalize_hash();
15054
+ });
14983
15055
 
14984
15056
  // ../plugin/src/features/magic-context/memory/embedding-local.ts
14985
- import { mkdirSync } from "node:fs";
15057
+ import { mkdirSync as mkdirSync2 } from "node:fs";
14986
15058
  import { open, stat, unlink, writeFile } from "node:fs/promises";
14987
- import { join as join3 } from "node:path";
15059
+ import { join as join2 } from "node:path";
14988
15060
  async function acquireModelLoadLock(lockPath) {
14989
15061
  const waitStart = Date.now();
14990
15062
  while (true) {
@@ -15099,7 +15171,7 @@ class LocalEmbeddingProvider {
15099
15171
  initPromise = null;
15100
15172
  constructor(model = DEFAULT_LOCAL_EMBEDDING_MODEL) {
15101
15173
  this.model = model;
15102
- this.modelId = `local:${model}`;
15174
+ this.modelId = getEmbeddingProviderIdentity({ provider: "local", model });
15103
15175
  }
15104
15176
  async initialize() {
15105
15177
  if (this.pipeline) {
@@ -15118,15 +15190,15 @@ class LocalEmbeddingProvider {
15118
15190
  if (LogLevel && "ERROR" in LogLevel) {
15119
15191
  env.logLevel = LogLevel.ERROR;
15120
15192
  }
15121
- const modelCacheDir = join3(getMagicContextStorageDir(), "models");
15193
+ const modelCacheDir = join2(getMagicContextStorageDir(), "models");
15122
15194
  try {
15123
- mkdirSync(modelCacheDir, { recursive: true });
15195
+ mkdirSync2(modelCacheDir, { recursive: true });
15124
15196
  env.cacheDir = modelCacheDir;
15125
15197
  } catch {
15126
15198
  log("[magic-context] could not create model cache dir, using library default");
15127
15199
  }
15128
15200
  const createPipeline = transformersModule.pipeline;
15129
- const lockPath = join3(modelCacheDir, ".load.lock");
15201
+ const lockPath = join2(modelCacheDir, ".load.lock");
15130
15202
  const releaseLock = await acquireModelLoadLock(lockPath);
15131
15203
  const stopHeartbeat = startLockHeartbeat(lockPath);
15132
15204
  try {
@@ -15241,12 +15313,13 @@ var init_embedding_local = __esm(() => {
15241
15313
  init_magic_context();
15242
15314
  init_data_path();
15243
15315
  init_logger();
15316
+ init_embedding_identity();
15244
15317
  STALE_LOCK_MS = 3 * 60000;
15245
15318
  MAX_LOCK_WAIT_MS = 5 * 60000;
15246
15319
  });
15247
15320
 
15248
15321
  // ../plugin/src/features/magic-context/memory/embedding-openai.ts
15249
- function normalizeEndpoint(endpoint) {
15322
+ function normalizeEndpoint2(endpoint) {
15250
15323
  return endpoint?.trim().replace(/\/+$/, "") ?? "";
15251
15324
  }
15252
15325
 
@@ -15261,10 +15334,15 @@ class OpenAICompatibleEmbeddingProvider {
15261
15334
  openLogged = false;
15262
15335
  halfOpenProbeInFlight = false;
15263
15336
  constructor(options) {
15264
- this.endpoint = normalizeEndpoint(options.endpoint);
15337
+ this.endpoint = normalizeEndpoint2(options.endpoint);
15265
15338
  this.model = options.model?.trim() ?? "";
15266
15339
  this.apiKey = options.apiKey?.trim() ?? "";
15267
- this.modelId = `openai-compat:${this.endpoint}:${this.model}`;
15340
+ this.modelId = getEmbeddingProviderIdentity({
15341
+ provider: "openai-compatible",
15342
+ endpoint: this.endpoint,
15343
+ model: this.model,
15344
+ ...this.apiKey ? { api_key: this.apiKey } : {}
15345
+ });
15268
15346
  }
15269
15347
  async initialize() {
15270
15348
  if (this.initialized)
@@ -15446,20 +15524,10 @@ class OpenAICompatibleEmbeddingProvider {
15446
15524
  var FAILURE_THRESHOLD = 3, FAILURE_WINDOW_MS = 60000, OPEN_DURATION_MS, FETCH_TIMEOUT_MS = 30000;
15447
15525
  var init_embedding_openai = __esm(() => {
15448
15526
  init_logger();
15527
+ init_embedding_identity();
15449
15528
  OPEN_DURATION_MS = 5 * 60000;
15450
15529
  });
15451
15530
 
15452
- // ../plugin/src/features/magic-context/memory/normalize-hash.ts
15453
- import { createHash } from "node:crypto";
15454
- function normalizeMemoryContent(content) {
15455
- return content.toLowerCase().replace(/\s+/g, " ").trim();
15456
- }
15457
- function computeNormalizedHash(content) {
15458
- const normalized = normalizeMemoryContent(content);
15459
- return createHash("md5").update(normalized).digest("hex");
15460
- }
15461
- var init_normalize_hash = () => {};
15462
-
15463
15531
  // ../plugin/src/features/magic-context/memory/storage-memory-embeddings.ts
15464
15532
  var exports_storage_memory_embeddings = {};
15465
15533
  __export(exports_storage_memory_embeddings, {
@@ -15604,17 +15672,8 @@ function resolveEmbeddingConfig(config2) {
15604
15672
  }
15605
15673
  return { provider: "off" };
15606
15674
  }
15607
- function resolveModelId(config2) {
15608
- if (config2.provider === "off") {
15609
- return "off";
15610
- }
15611
- if (config2.provider === "openai-compatible") {
15612
- const endpoint = config2.endpoint.trim();
15613
- const model = config2.model.trim();
15614
- const keyHash = config2.api_key ? computeNormalizedHash(config2.api_key) : "nokey";
15615
- return `openai-compat:${endpoint}:${model}:${keyHash}`;
15616
- }
15617
- return config2.model.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL;
15675
+ function resolveProviderIdentity(config2) {
15676
+ return getEmbeddingProviderIdentity(config2);
15618
15677
  }
15619
15678
  function createProvider(config2) {
15620
15679
  if (config2.provider === "off") {
@@ -15638,10 +15697,10 @@ function getOrCreateProvider() {
15638
15697
  }
15639
15698
  function initializeEmbedding(config2) {
15640
15699
  const nextConfig = resolveEmbeddingConfig(config2);
15641
- const nextModelId = resolveModelId(nextConfig);
15700
+ const nextProviderIdentity = resolveProviderIdentity(nextConfig);
15642
15701
  const previousProvider = provider;
15643
- const previousModelId = previousProvider?.modelId ?? resolveModelId(embeddingConfig);
15644
- if (previousModelId === nextModelId) {
15702
+ const previousProviderIdentity = previousProvider?.modelId ?? resolveProviderIdentity(embeddingConfig);
15703
+ if (previousProviderIdentity === nextProviderIdentity) {
15645
15704
  embeddingConfig = nextConfig;
15646
15705
  return;
15647
15706
  }
@@ -15789,9 +15848,9 @@ var DEFAULT_EMBEDDING_CONFIG, embeddingConfig, provider = null, loadUnembeddedMe
15789
15848
  var init_embedding = __esm(() => {
15790
15849
  init_magic_context();
15791
15850
  init_logger();
15851
+ init_embedding_identity();
15792
15852
  init_embedding_local();
15793
15853
  init_embedding_openai();
15794
- init_normalize_hash();
15795
15854
  init_storage_memory_embeddings();
15796
15855
  DEFAULT_EMBEDDING_CONFIG = {
15797
15856
  provider: "local",
@@ -15983,7 +16042,7 @@ var exports_native_binding = {};
15983
16042
  __export(exports_native_binding, {
15984
16043
  resolveBetterSqliteNativeBinding: () => resolveBetterSqliteNativeBinding
15985
16044
  });
15986
- import { existsSync, mkdirSync as mkdirSync2, writeFileSync } from "node:fs";
16045
+ import { existsSync, mkdirSync as mkdirSync3, writeFileSync } from "node:fs";
15987
16046
  import { createRequire } from "node:module";
15988
16047
  import * as path3 from "node:path";
15989
16048
  function logInfo(message) {
@@ -16083,7 +16142,7 @@ async function resolveBetterSqliteNativeBinding() {
16083
16142
  }
16084
16143
  logWarn(`cached binary at ${cachedPath} has wrong ABI (${cachedProbe.actual ?? "unknown"} != ${expected}); refetching`);
16085
16144
  }
16086
- mkdirSync2(path3.dirname(cachedPath), { recursive: true });
16145
+ mkdirSync3(path3.dirname(cachedPath), { recursive: true });
16087
16146
  const nodeFileBytes = await downloadElectronPrebuild(pkgVersion, expected);
16088
16147
  writeFileSync(cachedPath, nodeFileBytes);
16089
16148
  logInfo(`cached Electron prebuild at ${cachedPath} (${(nodeFileBytes.length / 1024).toFixed(1)} KB)`);
@@ -23900,14 +23959,6 @@ var require_src2 = __commonJS((exports, module) => {
23900
23959
  });
23901
23960
 
23902
23961
  // ../plugin/src/hooks/magic-context/read-session-db.ts
23903
- var exports_read_session_db = {};
23904
- __export(exports_read_session_db, {
23905
- withReadOnlySessionDb: () => withReadOnlySessionDb,
23906
- getRawSessionMessageCountFromDb: () => getRawSessionMessageCountFromDb,
23907
- getMessageTimesFromOpenCodeDb: () => getMessageTimesFromOpenCodeDb,
23908
- findLastAssistantModelFromOpenCodeDb: () => findLastAssistantModelFromOpenCodeDb,
23909
- closeReadOnlySessionDb: () => closeReadOnlySessionDb
23910
- });
23911
23962
  import { join as join8 } from "node:path";
23912
23963
  function getOpenCodeDbPath() {
23913
23964
  return join8(getDataDir(), "opencode", "opencode.db");
@@ -23937,9 +23988,6 @@ function getReadOnlySessionDb() {
23937
23988
  function withReadOnlySessionDb(fn) {
23938
23989
  return fn(getReadOnlySessionDb());
23939
23990
  }
23940
- function closeReadOnlySessionDb() {
23941
- closeCachedReadOnlyDb();
23942
- }
23943
23991
  function getRawSessionMessageCountFromDb(db, sessionId) {
23944
23992
  const row = db.prepare(`SELECT COUNT(*) as count FROM message WHERE session_id = ?
23945
23993
  AND NOT (COALESCE(json_extract(data, '$.summary'), 0) = 1
@@ -23965,34 +24013,6 @@ function getMessageTimesFromOpenCodeDb(sessionId, messageIds) {
23965
24013
  }
23966
24014
  return result;
23967
24015
  }
23968
- function findLastAssistantModelFromOpenCodeDb(sessionId) {
23969
- try {
23970
- return withReadOnlySessionDb((db) => {
23971
- const row = db.prepare(`SELECT json_extract(data, '$.providerID') as providerID,
23972
- json_extract(data, '$.modelID') as modelID,
23973
- json_extract(data, '$.agent') as agent
23974
- FROM message
23975
- WHERE session_id = ?
23976
- AND json_extract(data, '$.role') = 'assistant'
23977
- AND json_extract(data, '$.providerID') IS NOT NULL
23978
- AND json_extract(data, '$.modelID') IS NOT NULL
23979
- ORDER BY time_created DESC
23980
- LIMIT 1`).get(sessionId);
23981
- if (!row || typeof row.providerID !== "string" || typeof row.modelID !== "string") {
23982
- return null;
23983
- }
23984
- const agent = typeof row.agent === "string" && row.agent.length > 0 ? row.agent : undefined;
23985
- return {
23986
- providerID: row.providerID,
23987
- modelID: row.modelID,
23988
- ...agent ? { agent } : {}
23989
- };
23990
- });
23991
- } catch (error51) {
23992
- log("[magic-context] failed to recover live model from OpenCode DB:", error51);
23993
- return null;
23994
- }
23995
- }
23996
24016
  var cachedReadOnlyDb = null;
23997
24017
  var init_read_session_db = __esm(async () => {
23998
24018
  init_data_path();
@@ -24005,7 +24025,7 @@ init_embedding();
24005
24025
 
24006
24026
  // ../plugin/src/features/magic-context/storage-db.ts
24007
24027
  init_data_path();
24008
- import { copyFileSync, cpSync, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "node:fs";
24028
+ import { copyFileSync, cpSync, existsSync as existsSync4, mkdirSync as mkdirSync4 } from "node:fs";
24009
24029
  import { join as join6 } from "node:path";
24010
24030
 
24011
24031
  // ../plugin/src/shared/error-message.ts
@@ -24088,6 +24108,119 @@ function safeString(value) {
24088
24108
  init_logger();
24089
24109
  await init_sqlite();
24090
24110
 
24111
+ // ../plugin/src/features/magic-context/key-files/project-key-files.ts
24112
+ init_logger();
24113
+ import { createHash as createHash2 } from "node:crypto";
24114
+ import { existsSync as existsSync2, readFileSync, realpathSync } from "node:fs";
24115
+ import { join as join4, resolve, sep } from "node:path";
24116
+ var MISSING_CONTENT_HASH = "<missing>";
24117
+ var ORPHAN_GRACE_MS = 7 * 24 * 60 * 60 * 1000;
24118
+ function sha256(input) {
24119
+ return createHash2("sha256").update(input).digest("hex");
24120
+ }
24121
+ function resolveProjectPath(directory) {
24122
+ if (!directory?.trim())
24123
+ return null;
24124
+ try {
24125
+ return realpathSync(directory);
24126
+ } catch {
24127
+ return resolve(directory);
24128
+ }
24129
+ }
24130
+ function rowToProjectKeyFile(row) {
24131
+ return {
24132
+ projectPath: String(row.project_path),
24133
+ path: String(row.path),
24134
+ content: String(row.content),
24135
+ contentHash: String(row.content_hash),
24136
+ localTokenEstimate: Number(row.local_token_estimate),
24137
+ generatedAt: Number(row.generated_at),
24138
+ generatedByModel: row.generated_by_model == null ? null : String(row.generated_by_model),
24139
+ generationConfigHash: String(row.generation_config_hash),
24140
+ staleReason: row.stale_reason == null ? null : String(row.stale_reason)
24141
+ };
24142
+ }
24143
+ function readCurrentKeyFiles(db, projectPath) {
24144
+ const resolvedProjectPath = resolveProjectPath(projectPath) ?? projectPath;
24145
+ const rows = db.prepare(`SELECT project_path, path, content, content_hash, local_token_estimate,
24146
+ generated_at, generated_by_model, generation_config_hash, stale_reason
24147
+ FROM project_key_files
24148
+ WHERE project_path = ?
24149
+ ORDER BY generated_at DESC, path ASC`).all(resolvedProjectPath);
24150
+ return rows.map(rowToProjectKeyFile);
24151
+ }
24152
+ function getKeyFilesVersion(db, projectPath) {
24153
+ const resolvedProjectPath = resolveProjectPath(projectPath) ?? projectPath;
24154
+ const row = db.prepare("SELECT version FROM project_key_files_version WHERE project_path = ?").get(resolvedProjectPath);
24155
+ return Number(row?.version ?? 0);
24156
+ }
24157
+ function bumpKeyFilesVersion(db, projectPath) {
24158
+ const resolvedProjectPath = resolveProjectPath(projectPath) ?? projectPath;
24159
+ db.prepare(`INSERT INTO project_key_files_version (project_path, version)
24160
+ VALUES (?, 1)
24161
+ ON CONFLICT(project_path) DO UPDATE SET version = version + 1`).run(resolvedProjectPath);
24162
+ return getKeyFilesVersion(db, resolvedProjectPath);
24163
+ }
24164
+ function resolveCommitFiles(projectPath, files) {
24165
+ return files.map((file2) => {
24166
+ try {
24167
+ const disk = readFileSync(join4(projectPath, file2.path));
24168
+ return {
24169
+ ...file2,
24170
+ contentHash: sha256(disk),
24171
+ staleReason: null
24172
+ };
24173
+ } catch {
24174
+ return {
24175
+ ...file2,
24176
+ contentHash: MISSING_CONTENT_HASH,
24177
+ staleReason: "missing"
24178
+ };
24179
+ }
24180
+ });
24181
+ }
24182
+ function insertResolvedKeyFiles(db, projectPath, files, generatedAt, generatedByModel, generationConfigHash) {
24183
+ const insert = db.prepare(`INSERT INTO project_key_files
24184
+ (project_path, path, content, content_hash, local_token_estimate,
24185
+ generated_at, generated_by_model, generation_config_hash, stale_reason)
24186
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
24187
+ for (const file2 of files) {
24188
+ insert.run(projectPath, file2.path, file2.content, file2.contentHash, file2.localTokenEstimate, generatedAt, generatedByModel, generationConfigHash, file2.staleReason);
24189
+ }
24190
+ }
24191
+ function deleteOrphanProjectKeyFiles(db, now = Date.now()) {
24192
+ const cutoff = now - ORPHAN_GRACE_MS;
24193
+ const rows = db.prepare(`SELECT project_path AS projectPath, MAX(generated_at) AS lastGen
24194
+ FROM project_key_files
24195
+ GROUP BY project_path
24196
+ HAVING lastGen < ?`).all(cutoff);
24197
+ let deletedProjects = 0;
24198
+ for (const row of rows) {
24199
+ if (existsSync2(row.projectPath))
24200
+ continue;
24201
+ try {
24202
+ db.prepare("DELETE FROM project_key_files WHERE project_path = ?").run(row.projectPath);
24203
+ db.prepare("DELETE FROM project_key_files_version WHERE project_path = ?").run(row.projectPath);
24204
+ deletedProjects++;
24205
+ } catch (error51) {
24206
+ log(`[key-files] orphan GC failed for ${row.projectPath}:`, error51);
24207
+ }
24208
+ }
24209
+ return deletedProjects;
24210
+ }
24211
+ function isRelativeProjectFile(projectPath, relativePath) {
24212
+ if (!relativePath || relativePath.startsWith("/") || relativePath.includes(".."))
24213
+ return false;
24214
+ try {
24215
+ const root = realpathSync(projectPath);
24216
+ const absPath = resolve(projectPath, relativePath);
24217
+ const real = realpathSync(absPath);
24218
+ return real.startsWith(root + sep) || real === root;
24219
+ } catch {
24220
+ return false;
24221
+ }
24222
+ }
24223
+
24091
24224
  // ../plugin/src/features/magic-context/migrations.ts
24092
24225
  init_logger();
24093
24226
  var MIGRATIONS = [
@@ -24223,6 +24356,9 @@ var MIGRATIONS = [
24223
24356
  embedding BLOB NOT NULL,
24224
24357
  model_id TEXT NOT NULL,
24225
24358
  created_at INTEGER NOT NULL,
24359
+ -- FK-cascade audit (v12): git_commit_embeddings.sha -> git_commits.sha
24360
+ -- uses ON DELETE CASCADE, so SQLite PRAGMA foreign_keys must be ON on
24361
+ -- every connection and v12 cleans historical orphan rows.
24226
24362
  FOREIGN KEY(sha) REFERENCES git_commits(sha) ON DELETE CASCADE
24227
24363
  );
24228
24364
 
@@ -24362,6 +24498,69 @@ var MIGRATIONS = [
24362
24498
  db.exec("ALTER TABLE session_meta ADD COLUMN todo_synthetic_state_json TEXT DEFAULT ''");
24363
24499
  }
24364
24500
  }
24501
+ },
24502
+ {
24503
+ version: 12,
24504
+ description: "Clean orphan rows from FK-cascade embedding tables",
24505
+ up: (db) => {
24506
+ const hasTable = (name) => Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name));
24507
+ const memoryEmbeddings = hasTable("memory_embeddings") ? db.prepare(`DELETE FROM memory_embeddings
24508
+ WHERE memory_id NOT IN (SELECT id FROM memories)`).run().changes : 0;
24509
+ log(`[migrations] v12 cleaned ${memoryEmbeddings} orphan memory_embeddings row(s)`);
24510
+ const gitCommitEmbeddings = hasTable("git_commit_embeddings") ? db.prepare(`DELETE FROM git_commit_embeddings
24511
+ WHERE sha NOT IN (SELECT sha FROM git_commits)`).run().changes : 0;
24512
+ log(`[migrations] v12 cleaned ${gitCommitEmbeddings} orphan git_commit_embeddings row(s)`);
24513
+ }
24514
+ },
24515
+ {
24516
+ version: 13,
24517
+ description: "Add pending_compaction_marker_state column for deferred marker drain",
24518
+ up: (db) => {
24519
+ const cols = db.prepare("PRAGMA table_info(session_meta)").all();
24520
+ if (!cols.some((c) => c.name === "pending_compaction_marker_state")) {
24521
+ db.exec("ALTER TABLE session_meta ADD COLUMN pending_compaction_marker_state TEXT");
24522
+ }
24523
+ }
24524
+ },
24525
+ {
24526
+ version: 14,
24527
+ description: "Add project-scoped key files and version counter",
24528
+ up: (db) => {
24529
+ db.exec(`
24530
+ CREATE TABLE IF NOT EXISTS project_key_files (
24531
+ project_path TEXT NOT NULL,
24532
+ path TEXT NOT NULL,
24533
+ content TEXT NOT NULL,
24534
+ content_hash TEXT NOT NULL,
24535
+ local_token_estimate INTEGER NOT NULL,
24536
+ generated_at INTEGER NOT NULL,
24537
+ generated_by_model TEXT,
24538
+ generation_config_hash TEXT NOT NULL,
24539
+ stale_reason TEXT,
24540
+ PRIMARY KEY (project_path, path)
24541
+ );
24542
+
24543
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_project
24544
+ ON project_key_files(project_path);
24545
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_generated_at
24546
+ ON project_key_files(project_path, generated_at);
24547
+
24548
+ CREATE TABLE IF NOT EXISTS project_key_files_version (
24549
+ project_path TEXT PRIMARY KEY,
24550
+ version INTEGER NOT NULL DEFAULT 0
24551
+ );
24552
+ `);
24553
+ }
24554
+ },
24555
+ {
24556
+ version: 15,
24557
+ description: "Add deferred_execute_state column for boundary execution drain",
24558
+ up: (db) => {
24559
+ const cols = db.prepare("PRAGMA table_info(session_meta)").all();
24560
+ if (!cols.some((c) => c.name === "deferred_execute_state")) {
24561
+ db.exec("ALTER TABLE session_meta ADD COLUMN deferred_execute_state TEXT");
24562
+ }
24563
+ }
24365
24564
  }
24366
24565
  ];
24367
24566
  function ensureMigrationsTable(db) {
@@ -157021,6 +157220,28 @@ function mergeCommitHashes(existing, next) {
157021
157220
  return merged;
157022
157221
  }
157023
157222
 
157223
+ // ../plugin/src/shared/stable-json.ts
157224
+ function stableStringify(value, seen = new WeakSet) {
157225
+ if (value === undefined)
157226
+ return "undefined";
157227
+ if (value === null || typeof value !== "object")
157228
+ return JSON.stringify(value) ?? String(value);
157229
+ if (seen.has(value))
157230
+ return '"[Circular]"';
157231
+ seen.add(value);
157232
+ if (Array.isArray(value)) {
157233
+ return `[${value.map((item) => stableStringify(item, seen)).join(",")}]`;
157234
+ }
157235
+ const entries = Object.entries(value).sort(([a], [b]) => {
157236
+ if (a < b)
157237
+ return -1;
157238
+ if (a > b)
157239
+ return 1;
157240
+ return 0;
157241
+ });
157242
+ return `{${entries.map(([key, child]) => `${JSON.stringify(key)}:${stableStringify(child, seen)}`).join(",")}}`;
157243
+ }
157244
+
157024
157245
  // ../plugin/src/features/magic-context/tool-definition-tokens.ts
157025
157246
  var measurements = new Map;
157026
157247
  var fingerprints = new Map;
@@ -157055,7 +157276,7 @@ function loadToolDefinitionMeasurements(db) {
157055
157276
  // ../plugin/src/features/magic-context/tool-owner-backfill.ts
157056
157277
  init_data_path();
157057
157278
  init_logger();
157058
- import { existsSync as existsSync2 } from "node:fs";
157279
+ import { existsSync as existsSync3 } from "node:fs";
157059
157280
  import { join as join5 } from "node:path";
157060
157281
  var LEASE_DURATION_MS = 5 * 60 * 1000;
157061
157282
  var LEASE_RENEWAL_MS = 60 * 1000;
@@ -157095,7 +157316,7 @@ function runToolOwnerBackfill(db) {
157095
157316
  return result;
157096
157317
  }
157097
157318
  const opencodeDbPath = resolveOpencodeDbPath();
157098
- if (!existsSync2(opencodeDbPath)) {
157319
+ if (!existsSync3(opencodeDbPath)) {
157099
157320
  log(`[backfill] OpenCode DB not found at ${opencodeDbPath} — marking all unbackfilled sessions as skipped. Lazy adoption (defense-in-depth) handles legacy rows at runtime.`);
157100
157321
  markAllUnbackfilledSessionsSkipped(db);
157101
157322
  result.sessionsSkippedNoOcDb = countSessionsByStatus(db, "skipped");
@@ -157306,18 +157527,18 @@ function resolveDatabasePath() {
157306
157527
  return { dbDir, dbPath: join6(dbDir, "context.db") };
157307
157528
  }
157308
157529
  function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
157309
- if (existsSync3(targetDbPath))
157530
+ if (existsSync4(targetDbPath))
157310
157531
  return;
157311
157532
  const legacyDir = getLegacyOpenCodeMagicContextStorageDir();
157312
157533
  const legacyDbPath = join6(legacyDir, "context.db");
157313
- if (!existsSync3(legacyDbPath))
157534
+ if (!existsSync4(legacyDbPath))
157314
157535
  return;
157315
157536
  log(`[magic-context] migrating legacy plugin storage: ${legacyDir} -> ${targetDbDir} (legacy left in place as backup)`);
157316
- mkdirSync3(targetDbDir, { recursive: true });
157537
+ mkdirSync4(targetDbDir, { recursive: true });
157317
157538
  for (const suffix of ["", "-wal", "-shm"]) {
157318
157539
  const src = `${legacyDbPath}${suffix}`;
157319
157540
  const dst = join6(targetDbDir, `context.db${suffix}`);
157320
- if (existsSync3(src)) {
157541
+ if (existsSync4(src)) {
157321
157542
  try {
157322
157543
  copyFileSync(src, dst);
157323
157544
  } catch (error51) {
@@ -157327,7 +157548,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
157327
157548
  }
157328
157549
  const legacyModelsDir = join6(legacyDir, "models");
157329
157550
  const targetModelsDir = join6(targetDbDir, "models");
157330
- if (existsSync3(legacyModelsDir) && !existsSync3(targetModelsDir)) {
157551
+ if (existsSync4(legacyModelsDir) && !existsSync4(targetModelsDir)) {
157331
157552
  try {
157332
157553
  cpSync(legacyModelsDir, targetModelsDir, { recursive: true });
157333
157554
  } catch (error51) {
@@ -157336,9 +157557,9 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
157336
157557
  }
157337
157558
  }
157338
157559
  function initializeDatabase(db) {
157560
+ db.exec("PRAGMA foreign_keys=ON");
157339
157561
  db.exec("PRAGMA journal_mode=WAL");
157340
157562
  db.exec("PRAGMA busy_timeout=5000");
157341
- db.exec("PRAGMA foreign_keys=ON");
157342
157563
  db.exec(`
157343
157564
  CREATE TABLE IF NOT EXISTS tags (
157344
157565
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -157436,6 +157657,9 @@ function initializeDatabase(db) {
157436
157657
  );
157437
157658
 
157438
157659
  CREATE TABLE IF NOT EXISTS memory_embeddings (
157660
+ -- FK-cascade audit (v12): memory_embeddings.memory_id -> memories.id
157661
+ -- uses ON DELETE CASCADE, so SQLite PRAGMA foreign_keys must be ON on
157662
+ -- every connection and v12 cleans historical orphan rows.
157439
157663
  memory_id INTEGER PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
157440
157664
  embedding BLOB NOT NULL,
157441
157665
  model_id TEXT
@@ -157472,6 +157696,26 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
157472
157696
  );
157473
157697
  CREATE INDEX IF NOT EXISTS idx_dream_runs_project ON dream_runs(project_path, finished_at DESC);
157474
157698
 
157699
+ CREATE TABLE IF NOT EXISTS project_key_files (
157700
+ project_path TEXT NOT NULL,
157701
+ path TEXT NOT NULL,
157702
+ content TEXT NOT NULL,
157703
+ content_hash TEXT NOT NULL,
157704
+ local_token_estimate INTEGER NOT NULL,
157705
+ generated_at INTEGER NOT NULL,
157706
+ generated_by_model TEXT,
157707
+ generation_config_hash TEXT NOT NULL,
157708
+ stale_reason TEXT,
157709
+ PRIMARY KEY (project_path, path)
157710
+ );
157711
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_project ON project_key_files(project_path);
157712
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_generated_at ON project_key_files(project_path, generated_at);
157713
+
157714
+ CREATE TABLE IF NOT EXISTS project_key_files_version (
157715
+ project_path TEXT PRIMARY KEY,
157716
+ version INTEGER NOT NULL DEFAULT 0
157717
+ );
157718
+
157475
157719
  -- (smart_notes: see note above; merged into unified notes table by migration v1)
157476
157720
 
157477
157721
  CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
@@ -157543,7 +157787,17 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
157543
157787
  system_prompt_hash TEXT DEFAULT '',
157544
157788
  memory_block_cache TEXT DEFAULT '',
157545
157789
  memory_block_count INTEGER DEFAULT 0,
157546
- memory_block_ids TEXT DEFAULT ''
157790
+ memory_block_ids TEXT DEFAULT '',
157791
+ -- pending_compaction_marker_state: intentionally NULLABLE without a
157792
+ -- default. Absence of a deferred marker is SQL NULL; presence is a
157793
+ -- valid JSON blob written via setPendingCompactionMarkerState.
157794
+ -- Excluded from healNullTextColumns. Readers filter IS NOT NULL AND
157795
+ -- != empty-string defensively. Plan v6 section 3.
157796
+ pending_compaction_marker_state TEXT,
157797
+ -- deferred_execute_state: intentionally NULLABLE without a default.
157798
+ -- Absence is SQL NULL; presence is a JSON blob written via
157799
+ -- setDeferredExecutePendingIfAbsent. Excluded from healNullTextColumns.
157800
+ deferred_execute_state TEXT
157547
157801
  );
157548
157802
 
157549
157803
  CREATE INDEX IF NOT EXISTS idx_tags_session_tag_number ON tags(session_id, tag_number);
@@ -157631,6 +157885,8 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
157631
157885
  ensureColumn(db, "session_meta", "recomp_partial_range_end", "INTEGER DEFAULT 0");
157632
157886
  ensureColumn(db, "session_meta", "detected_context_limit", "INTEGER DEFAULT 0");
157633
157887
  ensureColumn(db, "session_meta", "needs_emergency_recovery", "INTEGER DEFAULT 0");
157888
+ ensureColumn(db, "session_meta", "pending_compaction_marker_state", "TEXT");
157889
+ ensureColumn(db, "session_meta", "deferred_execute_state", "TEXT");
157634
157890
  ensureColumn(db, "tags", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
157635
157891
  ensureColumn(db, "pending_ops", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
157636
157892
  ensureColumn(db, "source_contents", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
@@ -157707,7 +157963,15 @@ function ensureColumn(db, table, column, definition) {
157707
157963
  if (rows.some((row) => row.name === column)) {
157708
157964
  return;
157709
157965
  }
157710
- db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
157966
+ try {
157967
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
157968
+ } catch (err) {
157969
+ const recheck = db.prepare(`PRAGMA table_info(${table})`).all();
157970
+ if (recheck.some((row) => row.name === column)) {
157971
+ return;
157972
+ }
157973
+ throw err;
157974
+ }
157711
157975
  }
157712
157976
  function openDatabase() {
157713
157977
  const { dbDir, dbPath } = resolveDatabasePath();
@@ -157720,10 +157984,15 @@ function openDatabase() {
157720
157984
  }
157721
157985
  try {
157722
157986
  migrateLegacyStorageIfNeeded(dbPath, dbDir);
157723
- mkdirSync3(dbDir, { recursive: true });
157987
+ mkdirSync4(dbDir, { recursive: true });
157724
157988
  const db = new Database(dbPath);
157725
157989
  initializeDatabase(db);
157726
157990
  runMigrations(db);
157991
+ try {
157992
+ deleteOrphanProjectKeyFiles(db);
157993
+ } catch (error51) {
157994
+ log(`[magic-context] key-files orphan GC failed: ${getErrorMessage(error51)}`);
157995
+ }
157727
157996
  try {
157728
157997
  runToolOwnerBackfill(db);
157729
157998
  } catch (error51) {
@@ -157742,33 +158011,19 @@ function openDatabase() {
157742
158011
  }
157743
158012
  }
157744
158013
 
157745
- // ../plugin/src/shared/harness.ts
157746
- var currentHarness = "opencode";
157747
- var harnessLocked = false;
157748
- function setHarness(value) {
157749
- if (harnessLocked && currentHarness !== value) {
157750
- throw new Error(`Magic Context: harness already locked to "${currentHarness}"; cannot change to "${value}"`);
157751
- }
157752
- currentHarness = value;
157753
- harnessLocked = true;
157754
- }
157755
- function getHarness() {
157756
- return currentHarness;
157757
- }
157758
-
157759
158014
  // src/subagent-entry.ts
157760
158015
  init_logger();
157761
158016
 
157762
158017
  // src/config/index.ts
157763
158018
  init_magic_context();
157764
- import { existsSync as existsSync5, readFileSync as readFileSync2 } from "node:fs";
158019
+ import { existsSync as existsSync6, readFileSync as readFileSync3 } from "node:fs";
157765
158020
  import { homedir as homedir3 } from "node:os";
157766
158021
  import { join as join7 } from "node:path";
157767
158022
 
157768
158023
  // ../plugin/src/config/variable.ts
157769
- import { existsSync as existsSync4, readFileSync } from "node:fs";
158024
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "node:fs";
157770
158025
  import { homedir as homedir2 } from "node:os";
157771
- import { dirname as dirname2, isAbsolute, resolve } from "node:path";
158026
+ import { dirname as dirname3, isAbsolute, resolve as resolve2 } from "node:path";
157772
158027
  var ENV_PATTERN = /\{env:([^}]+)\}/g;
157773
158028
  var FILE_PATTERN = /\{file:([^}]+)\}/g;
157774
158029
  function substituteConfigVariables(input) {
@@ -157787,7 +158042,7 @@ function substituteConfigVariables(input) {
157787
158042
  if (fileMatches.length === 0) {
157788
158043
  return { text, warnings };
157789
158044
  }
157790
- const configDir = input.configPath ? dirname2(input.configPath) : process.cwd();
158045
+ const configDir = input.configPath ? dirname3(input.configPath) : process.cwd();
157791
158046
  let output = "";
157792
158047
  let cursor = 0;
157793
158048
  for (const match of fileMatches) {
@@ -157805,17 +158060,17 @@ function substituteConfigVariables(input) {
157805
158060
  }
157806
158061
  let filePath = rawPath.trim();
157807
158062
  if (filePath.startsWith("~/")) {
157808
- filePath = resolve(homedir2(), filePath.slice(2));
158063
+ filePath = resolve2(homedir2(), filePath.slice(2));
157809
158064
  } else if (!isAbsolute(filePath)) {
157810
- filePath = resolve(configDir, filePath);
158065
+ filePath = resolve2(configDir, filePath);
157811
158066
  }
157812
- if (!existsSync4(filePath)) {
158067
+ if (!existsSync5(filePath)) {
157813
158068
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
157814
158069
  continue;
157815
158070
  }
157816
158071
  let contents;
157817
158072
  try {
157818
- contents = readFileSync(filePath, "utf-8").trim();
158073
+ contents = readFileSync2(filePath, "utf-8").trim();
157819
158074
  } catch (error51) {
157820
158075
  const message = error51 instanceof Error ? error51.message : String(error51);
157821
158076
  warnings.push(`Failed to read file for ${token} (${filePath}): ${message}; using empty string`);
@@ -157840,11 +158095,11 @@ function getUserConfigPaths() {
157840
158095
  return [`${basePath}.jsonc`, `${basePath}.json`];
157841
158096
  }
157842
158097
  function resolveFirstExisting(paths) {
157843
- return paths.find((path4) => existsSync5(path4));
158098
+ return paths.find((path4) => existsSync6(path4));
157844
158099
  }
157845
158100
  function loadConfigFile(path4, scope) {
157846
158101
  try {
157847
- const rawText = readFileSync2(path4, "utf-8");
158102
+ const rawText = readFileSync3(path4, "utf-8");
157848
158103
  const substituted = substituteConfigVariables({
157849
158104
  text: rawText,
157850
158105
  configPath: path4
@@ -162453,6 +162708,61 @@ __export(exports_typebox, {
162453
162708
  Any: () => Any
162454
162709
  });
162455
162710
  // src/read-session-pi.ts
162711
+ function isMidTurnPi(event, _sessionId) {
162712
+ const messages = event?.messages;
162713
+ if (!Array.isArray(messages))
162714
+ return false;
162715
+ let latestAssistantIndex = -1;
162716
+ let latestAssistant = null;
162717
+ for (let i = messages.length - 1;i >= 0; i--) {
162718
+ const msg = messages[i];
162719
+ if (msg !== null && typeof msg === "object") {
162720
+ const record4 = msg;
162721
+ if (record4.role === "assistant") {
162722
+ latestAssistantIndex = i;
162723
+ latestAssistant = record4;
162724
+ break;
162725
+ }
162726
+ }
162727
+ }
162728
+ if (latestAssistant === null)
162729
+ return false;
162730
+ if (latestAssistant.stopReason === "toolUse")
162731
+ return true;
162732
+ const toolCallIds = getToolCallIds(latestAssistant.content);
162733
+ if (toolCallIds.size === 0)
162734
+ return false;
162735
+ const pairedToolResultIds = new Set;
162736
+ for (const msg of messages.slice(latestAssistantIndex + 1)) {
162737
+ if (msg === null || typeof msg !== "object")
162738
+ continue;
162739
+ const record4 = msg;
162740
+ if (record4.role !== "toolResult")
162741
+ continue;
162742
+ if (typeof record4.toolCallId === "string") {
162743
+ pairedToolResultIds.add(record4.toolCallId);
162744
+ }
162745
+ }
162746
+ for (const id of toolCallIds) {
162747
+ if (!pairedToolResultIds.has(id))
162748
+ return true;
162749
+ }
162750
+ return false;
162751
+ }
162752
+ function getToolCallIds(content) {
162753
+ const ids = new Set;
162754
+ if (!Array.isArray(content))
162755
+ return ids;
162756
+ for (const item of content) {
162757
+ if (item === null || typeof item !== "object")
162758
+ continue;
162759
+ const record4 = item;
162760
+ if (record4.type === "toolCall" && typeof record4.id === "string") {
162761
+ ids.add(record4.id);
162762
+ }
162763
+ }
162764
+ return ids;
162765
+ }
162456
162766
  function readPiSessionMessages(ctx) {
162457
162767
  const sm = ctx.sessionManager;
162458
162768
  if (sm === undefined)
@@ -162474,6 +162784,7 @@ function convertEntriesToRawMessages(entries) {
162474
162784
  const result = [];
162475
162785
  let nextOrdinal = 1;
162476
162786
  let pendingToolParts = [];
162787
+ let pendingFirstRealId = "";
162477
162788
  for (const entry of entries) {
162478
162789
  if (!isMessageEntry(entry)) {
162479
162790
  continue;
@@ -162482,6 +162793,9 @@ function convertEntriesToRawMessages(entries) {
162482
162793
  const role = msg.role;
162483
162794
  if (role === "toolResult") {
162484
162795
  pendingToolParts.push(...synthesizeToolResultParts(msg));
162796
+ if (pendingFirstRealId === "") {
162797
+ pendingFirstRealId = entry.id;
162798
+ }
162485
162799
  continue;
162486
162800
  }
162487
162801
  if (role === "user") {
@@ -162490,6 +162804,7 @@ function convertEntriesToRawMessages(entries) {
162490
162804
  ...synthesizeUserParts(msg)
162491
162805
  ];
162492
162806
  pendingToolParts = [];
162807
+ pendingFirstRealId = "";
162493
162808
  result.push({
162494
162809
  ordinal: nextOrdinal++,
162495
162810
  id: entry.id,
@@ -162502,11 +162817,12 @@ function convertEntriesToRawMessages(entries) {
162502
162817
  if (pendingToolParts.length > 0) {
162503
162818
  result.push({
162504
162819
  ordinal: nextOrdinal++,
162505
- id: "",
162820
+ id: pendingFirstRealId,
162506
162821
  role: "user",
162507
162822
  parts: pendingToolParts
162508
162823
  });
162509
162824
  pendingToolParts = [];
162825
+ pendingFirstRealId = "";
162510
162826
  }
162511
162827
  result.push({
162512
162828
  ordinal: nextOrdinal++,
@@ -162526,7 +162842,7 @@ function convertEntriesToRawMessages(entries) {
162526
162842
  if (pendingToolParts.length > 0) {
162527
162843
  result.push({
162528
162844
  ordinal: nextOrdinal,
162529
- id: "",
162845
+ id: pendingFirstRealId,
162530
162846
  role: "user",
162531
162847
  parts: pendingToolParts
162532
162848
  });
@@ -163027,7 +163343,7 @@ init_normalize_hash();
163027
163343
 
163028
163344
  // ../plugin/src/features/magic-context/memory/project-identity.ts
163029
163345
  import { execSync } from "node:child_process";
163030
- import { createHash as createHash2 } from "node:crypto";
163346
+ import { createHash as createHash3 } from "node:crypto";
163031
163347
  import path4 from "node:path";
163032
163348
  var GIT_TIMEOUT_MS = 5000;
163033
163349
  var resolvedCache = new Map;
@@ -163048,7 +163364,7 @@ function getRootCommitHash(directory) {
163048
163364
  }
163049
163365
  function directoryFallback(directory) {
163050
163366
  const canonical = path4.resolve(directory);
163051
- const hash2 = createHash2("md5").update(canonical).digest("hex").slice(0, 12);
163367
+ const hash2 = createHash3("md5").update(canonical).digest("hex").slice(0, 12);
163052
163368
  return `dir:${hash2}`;
163053
163369
  }
163054
163370
  function resolveProjectIdentity(directory) {
@@ -164113,6 +164429,12 @@ function isPersistedNoteNudgeRow(row) {
164113
164429
  const r = row;
164114
164430
  return typeof r.note_nudge_trigger_pending === "number" && typeof r.note_nudge_trigger_message_id === "string" && typeof r.note_nudge_sticky_text === "string" && typeof r.note_nudge_sticky_message_id === "string";
164115
164431
  }
164432
+ function isPersistedTodoSyntheticAnchorRow(row) {
164433
+ if (row === null || typeof row !== "object")
164434
+ return false;
164435
+ const r = row;
164436
+ return typeof r.todo_synthetic_call_id === "string" && typeof r.todo_synthetic_anchor_message_id === "string" && typeof r.todo_synthetic_state_json === "string";
164437
+ }
164116
164438
  function isPersistedHistorianFailureRow(row) {
164117
164439
  if (row === null || typeof row !== "object")
164118
164440
  return false;
@@ -164189,6 +164511,29 @@ function setPersistedDeliveredNoteNudge(db, sessionId, text, messageId = "") {
164189
164511
  function clearPersistedNoteNudge(db, sessionId) {
164190
164512
  db.prepare("UPDATE session_meta SET note_nudge_trigger_pending = 0, note_nudge_trigger_message_id = '', note_nudge_sticky_text = '', note_nudge_sticky_message_id = '' WHERE session_id = ?").run(sessionId);
164191
164513
  }
164514
+ function getPersistedTodoSyntheticAnchor(db, sessionId) {
164515
+ const result = db.prepare("SELECT todo_synthetic_call_id, todo_synthetic_anchor_message_id, todo_synthetic_state_json FROM session_meta WHERE session_id = ?").get(sessionId);
164516
+ if (!isPersistedTodoSyntheticAnchorRow(result)) {
164517
+ return null;
164518
+ }
164519
+ if (result.todo_synthetic_call_id.length === 0 || result.todo_synthetic_anchor_message_id.length === 0) {
164520
+ return null;
164521
+ }
164522
+ return {
164523
+ callId: result.todo_synthetic_call_id,
164524
+ messageId: result.todo_synthetic_anchor_message_id,
164525
+ stateJson: result.todo_synthetic_state_json
164526
+ };
164527
+ }
164528
+ function setPersistedTodoSyntheticAnchor(db, sessionId, callId, messageId, stateJson) {
164529
+ db.transaction(() => {
164530
+ ensureSessionMetaRow(db, sessionId);
164531
+ db.prepare("UPDATE session_meta SET todo_synthetic_call_id = ?, todo_synthetic_anchor_message_id = ?, todo_synthetic_state_json = ? WHERE session_id = ?").run(callId, messageId, stateJson, sessionId);
164532
+ })();
164533
+ }
164534
+ function clearPersistedTodoSyntheticAnchor(db, sessionId) {
164535
+ db.prepare("UPDATE session_meta SET todo_synthetic_call_id = '', todo_synthetic_anchor_message_id = '', todo_synthetic_state_json = '' WHERE session_id = ?").run(sessionId);
164536
+ }
164192
164537
  function getNoteLastReadAt(db, sessionId) {
164193
164538
  try {
164194
164539
  const result = db.prepare("SELECT note_last_read_at FROM session_meta WHERE session_id = ?").get(sessionId);
@@ -164293,6 +164638,52 @@ function setStrippedPlaceholderIds(db, sessionId, ids) {
164293
164638
  const json2 = ids.size > 0 ? JSON.stringify([...ids]) : "";
164294
164639
  db.prepare("UPDATE session_meta SET stripped_placeholder_ids = ? WHERE session_id = ?").run(json2, sessionId);
164295
164640
  }
164641
+ function isPendingCompactionMarker(value) {
164642
+ return typeof value === "object" && value !== null && typeof value.ordinal === "number" && typeof value.endMessageId === "string" && typeof value.publishedAt === "number";
164643
+ }
164644
+ function getPendingCompactionMarkerState(db, sessionId) {
164645
+ const row = db.prepare("SELECT pending_compaction_marker_state FROM session_meta WHERE session_id = ?").get(sessionId);
164646
+ const raw = row?.pending_compaction_marker_state;
164647
+ if (raw === null || raw === undefined || raw === "")
164648
+ return null;
164649
+ try {
164650
+ const parsed = JSON.parse(raw);
164651
+ if (isPendingCompactionMarker(parsed)) {
164652
+ return parsed;
164653
+ }
164654
+ } catch {}
164655
+ return null;
164656
+ }
164657
+ function clearPendingCompactionMarkerStateIf(db, sessionId, expected) {
164658
+ const expectedBlob = stableStringify(expected);
164659
+ const result = db.prepare(`UPDATE session_meta SET pending_compaction_marker_state = NULL
164660
+ WHERE session_id = ? AND pending_compaction_marker_state = ?`).run(sessionId, expectedBlob);
164661
+ return result.changes > 0;
164662
+ }
164663
+ function peekDeferredExecutePending(db, sessionId) {
164664
+ const row = db.prepare("SELECT deferred_execute_state FROM session_meta WHERE session_id = ?").get(sessionId);
164665
+ const raw = row?.deferred_execute_state;
164666
+ if (raw === null || raw === undefined || raw === "")
164667
+ return null;
164668
+ try {
164669
+ return JSON.parse(raw);
164670
+ } catch {
164671
+ return null;
164672
+ }
164673
+ }
164674
+ function setDeferredExecutePendingIfAbsent(db, sessionId, payload) {
164675
+ ensureSessionMetaRow(db, sessionId);
164676
+ const payloadBlob = stableStringify(payload);
164677
+ const result = db.prepare(`UPDATE session_meta SET deferred_execute_state = ?
164678
+ WHERE session_id = ? AND deferred_execute_state IS NULL`).run(payloadBlob, sessionId);
164679
+ return result.changes > 0;
164680
+ }
164681
+ function clearDeferredExecutePendingIfMatches(db, sessionId, expected) {
164682
+ const expectedBlob = stableStringify(expected);
164683
+ const result = db.prepare(`UPDATE session_meta SET deferred_execute_state = NULL
164684
+ WHERE session_id = ? AND deferred_execute_state = ?`).run(sessionId, expectedBlob);
164685
+ return result.changes > 0;
164686
+ }
164296
164687
  // ../plugin/src/features/magic-context/resolve-subagent-fallback.ts
164297
164688
  init_logger();
164298
164689
  await init_read_session_db();
@@ -166209,16 +166600,24 @@ function trimMemoriesToBudget(sessionId, memories, budgetTokens) {
166209
166600
  function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, projectPath, injectionBudgetTokens, temporalAwareness) {
166210
166601
  const cached2 = injectionCache.get(sessionId);
166211
166602
  if (!isCacheBusting && cached2) {
166212
- if (cached2.compartmentEndMessageId.length > 0) {
166213
- const cutoffIndex2 = messages.findIndex((message) => message.info.id === cached2.compartmentEndMessageId);
166214
- if (cutoffIndex2 >= 0) {
166215
- const remaining = messages.slice(cutoffIndex2 + 1);
166216
- messages.splice(0, messages.length, ...remaining);
166217
- } else {
166218
- sessionLog(sessionId, `compartment injection: cached boundary ${cached2.compartmentEndMessageId} not in messages (already trimmed), reusing cache`);
166603
+ if (cached2.kind === "empty") {
166604
+ return null;
166605
+ }
166606
+ const prepared = cached2.injection;
166607
+ if (prepared.compartmentEndMessageId === null) {
166608
+ sessionLog(sessionId, "compartment injection cache in degraded mode (null boundary), forcing rebuild");
166609
+ } else {
166610
+ if (prepared.compartmentEndMessageId.length > 0) {
166611
+ const cutoffIndex2 = messages.findIndex((message) => message.info.id === prepared.compartmentEndMessageId);
166612
+ if (cutoffIndex2 >= 0) {
166613
+ const remaining = messages.slice(cutoffIndex2 + 1);
166614
+ messages.splice(0, messages.length, ...remaining);
166615
+ } else {
166616
+ sessionLog(sessionId, `compartment injection: cached boundary ${prepared.compartmentEndMessageId} not in messages (already trimmed), reusing cache`);
166617
+ }
166219
166618
  }
166619
+ return { ...prepared, rebuiltFromDb: false };
166220
166620
  }
166221
- return cached2;
166222
166621
  }
166223
166622
  const compartments = getCompartments(db, sessionId);
166224
166623
  const facts = getSessionFacts(db, sessionId);
@@ -166250,7 +166649,11 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
166250
166649
  }
166251
166650
  }
166252
166651
  if (compartments.length === 0 && facts.length === 0 && !memoryBlock) {
166253
- injectionCache.delete(sessionId);
166652
+ injectionCache.set(sessionId, {
166653
+ kind: "empty",
166654
+ compartmentEndMessageId: "",
166655
+ renderedBytes: 0
166656
+ });
166254
166657
  return null;
166255
166658
  }
166256
166659
  let dateRanges;
@@ -166283,9 +166686,10 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
166283
166686
  compartmentCount: 0,
166284
166687
  skippedVisibleMessages: 0,
166285
166688
  factCount: facts.length,
166286
- memoryCount
166689
+ memoryCount,
166690
+ rebuiltFromDb: true
166287
166691
  };
166288
- injectionCache.set(sessionId, result2);
166692
+ injectionCache.set(sessionId, { kind: "populated", injection: result2 });
166289
166693
  return result2;
166290
166694
  }
166291
166695
  const lastCompartment = compartments[compartments.length - 1];
@@ -166303,9 +166707,10 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
166303
166707
  compartmentCount: compartments.length,
166304
166708
  skippedVisibleMessages: 0,
166305
166709
  factCount: facts.length,
166306
- memoryCount
166710
+ memoryCount,
166711
+ rebuiltFromDb: true
166307
166712
  };
166308
- injectionCache.set(sessionId, result2);
166713
+ injectionCache.set(sessionId, { kind: "populated", injection: result2 });
166309
166714
  return result2;
166310
166715
  }
166311
166716
  let skippedVisibleMessages = 0;
@@ -166314,17 +166719,20 @@ function prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, pr
166314
166719
  skippedVisibleMessages = cutoffIndex + 1;
166315
166720
  const remaining = messages.slice(cutoffIndex + 1);
166316
166721
  messages.splice(0, messages.length, ...remaining);
166722
+ } else {
166723
+ sessionLog(sessionId, `compartment injection entering degraded mode: boundary ${lastEndMessageId} not in visible messages`);
166317
166724
  }
166318
166725
  const result = {
166319
166726
  block,
166320
166727
  compartmentEndMessage: lastEnd,
166321
- compartmentEndMessageId: lastEndMessageId,
166728
+ compartmentEndMessageId: cutoffIndex >= 0 ? lastEndMessageId : null,
166322
166729
  compartmentCount: compartments.length,
166323
166730
  skippedVisibleMessages,
166324
166731
  factCount: facts.length,
166325
- memoryCount
166732
+ memoryCount,
166733
+ rebuiltFromDb: true
166326
166734
  };
166327
- injectionCache.set(sessionId, result);
166735
+ injectionCache.set(sessionId, { kind: "populated", injection: result });
166328
166736
  return result;
166329
166737
  }
166330
166738
 
@@ -166448,6 +166856,70 @@ function createCtxSearchTool(deps) {
166448
166856
  };
166449
166857
  }
166450
166858
 
166859
+ // src/tools/todowrite.ts
166860
+ var STATUS_VALUES = [
166861
+ "pending",
166862
+ "in_progress",
166863
+ "completed",
166864
+ "cancelled"
166865
+ ];
166866
+ var PRIORITY_VALUES = ["high", "medium", "low"];
166867
+ var TodoItem = exports_typebox.Object({
166868
+ content: exports_typebox.String({ description: "Brief description of the task" }),
166869
+ status: exports_typebox.Union(STATUS_VALUES.map((v) => exports_typebox.Literal(v))),
166870
+ priority: exports_typebox.Optional(exports_typebox.Union(PRIORITY_VALUES.map((v) => exports_typebox.Literal(v)))),
166871
+ id: exports_typebox.Optional(exports_typebox.String({ description: "Optional stable id for the todo" }))
166872
+ });
166873
+ var TodowriteParams = exports_typebox.Object({
166874
+ todos: exports_typebox.Array(TodoItem, {
166875
+ description: "Replace the current task list with this complete set of todos. Include every task you intend to track this turn — pending, in_progress, completed, or cancelled — because the list overwrites previous state."
166876
+ })
166877
+ });
166878
+ var TOOL_DESCRIPTION = [
166879
+ "Manage your task list for this session.",
166880
+ "",
166881
+ "Use this tool to plan multi-step work, track in-flight tasks, and",
166882
+ "mark progress as you complete steps. Pass the COMPLETE updated list",
166883
+ "of todos every time — this tool replaces the prior list rather than",
166884
+ "appending to it.",
166885
+ "",
166886
+ "Task states:",
166887
+ " - pending: not started yet",
166888
+ " - in_progress: currently working on (limit to ONE task at a time)",
166889
+ " - completed: finished successfully",
166890
+ " - cancelled: no longer needed",
166891
+ "",
166892
+ "Use this tool proactively for non-trivial work spanning 3+ steps.",
166893
+ "Skip it for single-shot answers or trivial 1-2 step tasks."
166894
+ ].join(`
166895
+ `);
166896
+ function createTodowriteTool() {
166897
+ return {
166898
+ name: "todowrite",
166899
+ label: "Todos",
166900
+ description: TOOL_DESCRIPTION,
166901
+ parameters: TodowriteParams,
166902
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
166903
+ const todos = params.todos ?? [];
166904
+ const completed = todos.filter((t) => t.status === "completed").length;
166905
+ const active = todos.length - completed;
166906
+ return {
166907
+ content: [
166908
+ {
166909
+ type: "text",
166910
+ text: JSON.stringify(todos, null, 2)
166911
+ }
166912
+ ],
166913
+ details: {
166914
+ todos,
166915
+ title: `${active} todos`,
166916
+ truncated: false
166917
+ }
166918
+ };
166919
+ }
166920
+ };
166921
+ }
166922
+
166451
166923
  // src/tools/index.ts
166452
166924
  function registerMagicContextTools(pi, opts) {
166453
166925
  pi.registerTool(createCtxSearchTool({
@@ -166467,6 +166939,7 @@ function registerMagicContextTools(pi, opts) {
166467
166939
  dreamerEnabled: opts.dreamerEnabled ?? false
166468
166940
  }));
166469
166941
  pi.registerTool(createCtxExpandTool({ db: opts.db }));
166942
+ pi.registerTool(createTodowriteTool());
166470
166943
  if (opts.ctxReduceEnabled === true) {
166471
166944
  pi.registerTool(createCtxReduceTool({
166472
166945
  db: opts.db,